diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a30cb642a46..032e1fee69c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,37 +1,76 @@ - - - + + + + ## What are the changes the user will see? ## Why am I making these changes? - - - + + ## What are the changes from a developer perspective? - - + -### Screenshots/Videos - - +## Screenshots/Videos + ## How to test the changes? - - - - + ## Checklist - [ ] **I'm using `beta` as my base branch** - [ ] There is no overlap with another PR? - [ ] The PR is self-contained and cannot be split into smaller PRs? - [ ] Have I provided a clear explanation of the changes? -- [ ] Have I considered writing automated tests for the issue? -- [ ] If I have text, did I make it translatable and add a key in the English locale file(s)? -- [ ] Have I tested the changes (manually)? - - [ ] Are all unit tests still passing? (`npm run test`) -- [ ] Are the changes visual? - - [ ] Have I provided screenshots/videos of the changes? +- [ ] Have I tested the changes manually? +- [ ] Are all unit tests still passing? (`npm run test`) + - [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes? +- [ ] Have I provided screenshots/videos of the changes (if applicable)? + - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? + +Are there any localization additions or changes? If so: +- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo? + - [ ] If so, please leave a link to it here: +- [ ] Has the translation team been contacted for proofreading/translation? \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 00000000000..0fe0a8dc290 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,620 @@ +# 🎵 Music + +## BGM +- Pokémon Mystery Dungeon: Explorers of Sky + - Arata Iiyoshi + - Hideki Sakamoto + - Keisuke Ito + - Ken-ichi Saito + - Yoshihiro Maeda +- Pokémon Black/White + - Go Ichinose + - Hitomi Sato + - Shota Kageyama +- Pokémon Mystery Dungeon: Rescue Team DX + - Keisuke Ito + - Arata Iiyoshi + - Atsuhiro Ishizuna +- Pokémon HeartGold/SoulSilver +- Pokémon Black/White 2 +- Pokémon X/Y +- Pokémon Omega Ruby/Alpha Sapphire +- Pokémon Sun/Moon +- Pokémon Ultra Sun/Ultra Moon +- Pokémon Sword/Shield +- Pokémon Legends: Arceus +- Pokémon Scarlet/Violet +- Firel (Custom Graveyard, Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music) +- Lmz (Custom Ancient Ruins, Jungle, and Lake biome music) +- Andr06 (Custom Forest, Slum and Sea biome music) +- _tresnoir +- unveiler + +## Sound Effects +- Pokémon Emerald +- Pokémon Black/White + + +# 🎨 Art + +## Backgrounds +- Squip (Paid Commissions) +- Contributions by Someonealive-QN + +## UI +- GAMEFREAK +- LJ Birdman + +## Pagefault Games Intro +- Spectremint + +## Game Logo +- Gonstar (Paid Commission) + +## Trainer Sprites +- GAMEFREAK (Pokémon Black/White 2, Pokémon Diamond/Pearl) +- kyledove +- Brumirage +- pkmn_realidea (Paid Commissions) +- IceJkai +- Leparagon +- wormhood + +## Mystery Event Sprites +- chrysomelinae +- koda_want_to_sleep +- “🐺Kieran/YJ 🐍” rival_kieran aka thedreadedden +- ImperialSympathizer +- wormhood +- gerolau +- otterwatch + +## Trainer Portraits +- pkmn_realidea (Paid Commissions) + +## Pokémon Sprites and Animation +In addition to the lists below, please check [the PokéRogue wiki](https://wiki.pokerogue.net/credits:credits) for a more detailed list of Pokémon Sprite credits. + +- GAMEFREAK (Pokémon Black/White 2) +- Smogon Sprite Project (Various Artists) +- Skyflyer +- Nolo33 +- Ebaru +- EricLostie +- kiriaura +- Caruban +- Sopita_Yorita +- Azrita +- AshnixsLaw +- Hellfire0raptor +- RetroNC +- Franark122k +- OldSoulja +- PKMarioG +- ItsYugen +- lucasomi +- Pkm Sinfonia +- Poki Papillon +- Fleimer_ +- bizcoeindoloro +- mangalos810 +- selstar + +### Static Sprites and Base Shiny Replacements Credits +- AMVictory +- Antiant +- Arhops +- arinoelle +- Arkeis +- aXl +- BananaToast +- Basic Vanillite +- BlackWhiteRobin +- Blaquaza +- Branflakes325 +- Brylark +- Buna +- Bynine +- Corson +- Cynda +- “Diashi” diazhi +- Dleep +- doomchaos +- Espeon Scientist +- Farriella +- fishbowlsoul90 +- “Follower” rulerofthesea11 +- Galifia +- GeoisEvil +- G.E.Z. +- Glustora +- Harrie +- HealnDeal +- Hematite +- HM100 +- Ice-cold Claws +- Involuntary Twitch +- “Jay” itsamejay +- KattenK +- KingOfThe-X-Roads +- KyleDove +- Kyleo +- Kyuzeth +- Larryturbo +- Layell +- Legitimate Username +- leParagon +- “LJ” lj_birdman +- Luigi Player +- Madmadness65 +- Mega-Pokebattlerz +- Mintly +- mjco +- “Momo” sphinx_sage +- MrDollSteak +- MyMarshlands +- N-Kin +- Noscium +- “Nova” fabunova +- “Omniv” omniv +- paintseagull +- princessofmusic +- PumpkinPastel +- Quanyails +- RadicalCharizard +- RedRooster +- “♂ROMEO⚧” gerolau +- ruleroftheseas11 +- SelenaArmorclaw +- “serif” serifaizawa +- Siiilver +- Sleet +- Snivy101 +- Speed-X +- Sphex +- Spook +- Squip +- TeraVolt +- TheAetherPlayer +- TheCynicalPoet +- Tooni +- TrainerSplash +- Travis +- Turtleye +- Tyrell D. Barnes +- “Vari” \_vari\_ +- Wobblebuns +- WolfPP +- WPS +- Wyverii +- “zan” smtif +- Zerudez +- Z-nogyroP + +### Animated Sprites Credits +- Antiant +- arinoelle +- Blaquaza +- Claire Starsword +- Coyotango +- DanEx +- “Diashi” diazhi +- GalacticArtistMuffin +- G.E.Z. +- hexagonereal +- HM100 +- Katten +- LeParagon +- localghost +- MallowOut +- mattiwarden +- “Momo” sphinx_sage +- N-Kin +- NoelleMBrooks +- Nyx +- “Omniv” omniv +- princessofmusic +- PumpkinPastel +- RadicalCharizard +- seleccion +- SelenaArmorclaw +- TheAetherPlayer +- Tinkatooni +- Typhlito +- uppa +- “Vari” \_vari\_ + +### Rare/Epic Shiny Variants Credits +- “Andr06” andr06 +- “Appo” appo +- “Ashhawk” k_redacted +- “Auralite” _auralite +- “Awesome_Soul” awesome_soul +- “Bagon” bagonganda +- “Bibble” nuts_. +- “BloomOfWoods” bloomofwoods +- “Buge” buge +- “bun” bunove +- “bukie” bukie +- cameranian +- “Caramel” saltedcarriemel +- “Chocolate Niblets” choconibs +- “CKC” ckc_ +- “ClawsHDi” clawshdi +- “Clown Princess” clown_princess +- “Cmac2173” cmac2173 +- “Corsola” corsola_bandit +- “Criminon” criminon +- “Cryptican Gogoat Enthusiast” crypticanexe +- “Diashi” diazhi +- “deviant.daffodil, bug enthusiast” deviant.daffodil +- “DigitalVaporeon” digitalvaporeon +- dingosig +- “Eku” ekusas00 +- “Elefante” +- “Esca” colossalsquid +- “Folf” folf. +- “Follower” ruleroftheseas11 +- “Fontbane” fontbane +- “Gallow” gallowhound +- “Giojoe” giojoe10 +- “Gonfold” gonfold +- greenninja757 +- “Grassy_Storm” grassy_storm +- “GreenMegaMan” greenmegaman +- “GROWL” myflixer.to +- “guy claiming to work on a guide” 7thatlas +- “h. 🍄” letterh. +- “hamez” .hamez +- “Hanniel” hanniel.15 +- “ImaginaryNeon” imaginaryneon +- “Jay” itsamejay +- “Jelke” jelke +- kalikimothy +- “🐺Kieran/YJ 🐍” rival_kieran aka thedreadedden +- “Koda” Koda_want_to_sleep +- “Lana” smogonian +- “LJ” lj_birdman +- “Long Girl” docamakesart +- “Lucky” luckyluckylucky +- “MissingNo.” clickonflareblitz +- “Momo” sphinx_sage +- monkehestman +- “Nexxus” nexxus_ +- “Nik :3” realniktrustme +- “Nikolatsu” +- “nora” ora.n +- “NOVA” fabunova +- officerporkchops +- “Omniv” omniv +- “Otterwatch” otterwatch_ +- “Pandoraz” pandoraz +- “Papa Pepsm An” papapepsman +- “Penguin” peng06 +- “Prodigy” lorekeeperprodigy +- “Purpenigma” purpenigma +- “Rage” ragerevival +- “♂ROMEO⚧” gerolau +- “Sagrell D'Arcadia” coffeerequired +- “serif” serifaizawa +- “SillyTopplingGoose” sillytopplinggoose +- “Splash Damage” splashceles +- “Sweg1b01” sweg1b01 +- “SyntheGrim” synthegrim +- “TaMenace” tamariontherestless +- “TheTRUEgge” thetruegge +- “Thorn” crownofthorns +- “Togepi” togepimax +- “Toopy” .toopy +- “Tristan” tristan.w +- “Umbreon” umbreon\_.\_ +- “Vari” \_vari\_ +- “Waasephi” +- wormhood +- “Yep, it's Caio” yepitscaio +- “Ymri” ymri +- “zaccie” zaccie +- “zan” smtif + +## Move Animations +- Pokémon Reborn + + +# ⚖️ Balance + +## Balance Team +- damocleas +- Blitzy aka Kazapple +- Cynthia_calliope +- Esca +- Fontbane +- Plasto +- Sethcurry +- Starkrieg + +## Past Members +- Swizzo +- Zaccie + +## Past Contributors +- chrysomelinae (Mystery Events) +- AsdarDevelops (Mystery Events) + + +# 💻 Development + +## Server Owner/Administrator +- pancakes aka patapancakes + +## Senior Developers +- Walker +- NightKev +- Moka +- Temp aka Tempo-anon +- Madmadness65 + +## Developers +- CodeTappert +- flx-sta +- innerthunder +- frutescens +- Opaquer +- SN34KZ +- Swain aka torranx + +## Junior Developers +- KimJeongSun +- ImperialSympathizer + +## Bug/Issue Managers +- Snailman +- Daleks +- Lily +- PigeonBar + +## Past Contributors +- Fontbane +- sodaMelon +- schmidtc1 +- shayebeadling +- DustinLin +- lucfd +- madibye +- EmberCM +- Mewtwo2387 +- hayuna +- sirzento +- ReneGV +- mattrossdev +- zacharied +- NxKarim +- td76099 +- Xiaphear +- j-diefenbach +- jaimefd +- EvasiveAce +- EmoUsedHM01 +- francktrouillez +- JakubHanko +- FredeX +- PigeonBar +- prime-dialga +- rnicar245 +- rationality6 +- Neverblade +- Corrade +- Admiral-Billy +- okimin +- Arxalc +- PrabbyDD +- JonStudders +- karl-police +- prateau +- meepen +- arColm +- allen925 +- InfernoVulpix +- snoozbuster +- zaccie +- happinyz +- PyGaVS +- mcmontag +- ElizaAlex +- AsdarDevelops +- Vassiat +- RedstonewolfX +- Sam/Flashfyre (initial developer, started PokéRogue) +- Greenlamp +- bennybroseph +- OrangeRed +- Dakurei +- Brain Frog + + +# 🌎 Translation + +## In-Game Translators + +### 🇩🇪 German (de) +- CodeTappert + +### 🇪🇸 Spanish (es-ES) +- Dan Stevenson +- Javi +- Lily Alterni +- Qyxgames + +### 🇫🇷 French (fr) +- Lugiadrien + +### 🇮🇹 Italian (it) +- Nicus + +### 🇯🇵 Japanese (ja) +- 6mozuke9 +- Chapybara +- PeachFresca + +### 🇰🇷 Korean (ko) +- Enoch +- KimJeongSun +- Returntoice +- sodamelon + +### 🇧🇷 Portuguese (pt-BR) +- Zé Ricardo + +### 🇨🇳 Chinese (zh-CN) +- dddsenic +- mercurius +- VittorioVeneto +- Yonmaru + +### 🇹🇼 Chinese (zh-TW) +- mercurius +- Seagull + +### Past contributors +- Asdar (es-ES) +- Rafa (es-ES) +- GINK-SS (ko) +- prostagma (pt-BR) +- Ei (zh-TW) + +## Wiki Translators + +### 🇪🇸 Spanish (es-ES) +- victorcooler + +### 🇫🇷 French (fr) +- Evan +- Mitsue +- Papier +- Sangara +- Voltarix + +### 🇮🇹 Italian (it) +- Purce +- T-reds + +### 🇰🇷 Korean (ko) +- LeKaaN +- Returntoice +- sodamelon + +### 🇵🇱 Polish (pl) +- Talo + +### 🇧🇷 Portuguese (pt-BR) +- Beast +- Sushi +- Zé Ricardo + +### 🇨🇳 Chinese (zh-CN) +- jw-0- + +### Past contributors +- Dietaube (de) +- Gnorpelltroll (de) +- xRegix (de) +- Broly Ikari (fr) +- Leo Edgar_Zimmer (fr) +- Telor (fr) +- dorri (ko) +- Little Moder_eldenring (ko) +- Andy (zh-CN) +- Black Feather (zh-CN) +- itschili (zh-CN) +- RimKnight (zh-CN) +- Yubari (zh-CN) + +## 🇺🇸 English Proofreaders +- Cheyu +- Faust +- HaywiredUp +- Irridescence +- Ke'ahi +- Louie +- Nully +- PeD +- The Programmer + +### Past contributors +- I... + + +# 📰 Wiki + +## Wiki Head +- H.A.R.V. + +## Wiki Lead +- Sangara +- Zac +- Smew +- Brain Frog +- Hannah + +## Editor +- Prodigy +- Akuma +- Dan Gioia +- Shimizoki +- Stave +- NalysArbur +- Ceimir +- Solanum Tuberosum +- Pom + +## Artist +- dub +- SmashMania +- Wren +- Lugiadrien + +## Contributor +- Daleks <3 +- Inferno Vulpix +- Embri +- Nekod +- P0kemonY +- Scoom +- BlueVaron + + +# ☎️ Discord + +## Head Moderator +- leah + +## Senior Moderators +- Solanum Tuberosum +- Madmadness65 +- Necrowmancer +- lana + +## Moderators +- Sethcurry + +## Junior Moderators +- chacolah +- ChaosGrimmon +- Cynthia +- Kat +- lyn +- Pom + + +# ✨ Special Thanks + +## Reddit Moderators +- TheZigglez +- Vicksin +- Sapphire +- Javi +- roi + +## External Tools +- Ydarissep (RogueDex) +- Admiral-Billy (Offline App - Desktop) +- Red aka StonedModder (iOS App) diff --git a/README.md b/README.md index 866687d54b7..607a42e5125 100644 --- a/README.md +++ b/README.md @@ -37,92 +37,6 @@ For detailed guidelines on documenting your code, refer to the [comments.md](./d Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us! # 📝 Credits -> If this project contains assets you have produced and you do not see your name here, **please** reach out. +> If this project contains assets you have produced and you do not see your name, **please** reach out, either [here on GitHub](https://github.com/pagefaultgames/pokerogue/issues/new) or via [Discord](https://discord.gg/pokerogue). -### 🎵 BGM - - Pokémon Mystery Dungeon: Explorers of Sky - - Arata Iiyoshi - - Hideki Sakamoto - - Keisuke Ito - - Ken-ichi Saito - - Yoshihiro Maeda - - Pokémon Black/White - - Go Ichinose - - Hitomi Sato - - Shota Kageyama - - Pokémon Mystery Dungeon: Rescue Team DX - - Keisuke Ito - - Arata Iiyoshi - - Atsuhiro Ishizuna - - Pokémon HeartGold/SoulSilver - - Pokémon Black/White 2 - - Pokémon X/Y - - Pokémon Omega Ruby/Alpha Sapphire - - Pokémon Sun/Moon - - Pokémon Ultra Sun/Ultra Moon - - Pokémon Sword/Shield - - Pokémon Legends: Arceus - - Pokémon Scarlet/Violet - - Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music) - - Lmz (Custom Ancient Ruins, Jungle, and Lake biome music) - - Andr06 (Custom Slum and Sea biome music) - -### 🎵 Sound Effects - - Pokémon Emerald - - Pokémon Black/White - -### 🎨 Backgrounds - - Squip (Paid Commissions) - - Contributions by Someonealive-QN - -### 🎨 UI - - GAMEFREAK - - LJ Birdman - -### 🎨 Pagefault Games Intro - - Spectremint - -### 🎨 Game Logo - - Gonstar (Paid Commission) - -### 🎨 Trainer Sprites - - GAMEFREAK (Pokémon Black/White 2, Pokémon Diamond/Pearl) - - kyledove - - Brumirage - - pkmn_realidea (Paid Commissions) - - IceJkai - -### 🎨 Trainer Portraits - - pkmn_realidea (Paid Commissions) - -### 🎨 Pokemon Sprites and Animation - - GAMEFREAK (Pokémon Black/White 2) - - Smogon Sprite Project (Various Artists) - - Skyflyer - - Nolo33 - - Ebaru - - EricLostie - - KingOfThe-X-Roads - - kiriaura - - Caruban - - Sopita_Yorita - - Azrita - - AshnixsLaw - - Hellfire0raptor - - RetroNC - - Franark122k - - OldSoulja - - PKMarioG - - ItsYugen - - lucasomi - - Pkm Sinfonia - - Poki Papillon - - Fleimer_ - - bizcoeindoloro - - mangalos810 - - Involuntary-Twitch - - selstar - - koda_want_to_sleep - -### 🎨 Move Animations - - Pokémon Reborn +Thank you to all the wonderful people that have contributed to the PokéRogue project! You can find the credits [here](./CREDITS.md). diff --git a/global.d.ts b/global.d.ts index f4dfa7d4cb2..c896a4983e4 100644 --- a/global.d.ts +++ b/global.d.ts @@ -10,5 +10,5 @@ declare global { * * To set up your own server in a test see `game_data.test.ts` */ - var i18nServer: SetupServerApi; + var server: SetupServerApi; } diff --git a/package-lock.json b/package-lock.json index 9e512884922..a4568b3f5ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.1.6", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.1.6", + "version": "1.3.0", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", @@ -16,6 +16,7 @@ "i18next-http-backend": "^2.6.1", "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", + "jszip": "^3.10.1", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" }, @@ -2723,6 +2724,11 @@ "node": ">= 0.6" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -4045,6 +4051,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4072,6 +4083,11 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/ini": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", @@ -4481,6 +4497,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4648,6 +4675,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -5237,6 +5272,11 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/papaparse": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", @@ -5485,6 +5525,11 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5551,6 +5596,25 @@ } ] }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -5741,6 +5805,11 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/safe-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", @@ -5800,6 +5869,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5917,6 +5991,14 @@ "dev": true, "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -6473,6 +6555,11 @@ "requires-port": "^1.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/vite": { "version": "5.4.8", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", diff --git a/package.json b/package.json index f106fb1a773..a8641bb0b98 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.1.6", + "version": "1.3.0", "type": "module", "scripts": { "start": "vite", @@ -55,6 +55,7 @@ "i18next-http-backend": "^2.6.1", "i18next-korean-postposition-processor": "^1.0.0", "json-stable-stringify": "^1.1.0", + "jszip": "^3.10.1", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" }, diff --git a/public/audio/bgm/forest.mp3 b/public/audio/bgm/forest.mp3 index a1d9ecb4b28..6382d3fc0b2 100644 Binary files a/public/audio/bgm/forest.mp3 and b/public/audio/bgm/forest.mp3 differ diff --git a/public/battle-anims/common-powder.json b/public/battle-anims/common-powder.json new file mode 100644 index 00000000000..698d68e7e81 --- /dev/null +++ b/public/battle-anims/common-powder.json @@ -0,0 +1,2496 @@ +{ + "graphic":"PRAS- Powder", + "frames":[ + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 23, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 23, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 21, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 23, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 22, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 0 , + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 23, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 17, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 23, + "opacity": 0 , + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 17, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 24, + "opacity": 0, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 17, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 8, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 8, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 18, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 8, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 8, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 20, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 8, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 19, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":16, + "y":2, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 20, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 20, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 9, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 20, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 10, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 11, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 12, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 13, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 14, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 15, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 255, + "priority": 1, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 155, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 150, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + }, + { + "x":-6, + "y":-3, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 155, + "focus":2 + }, + { + "x":12, + "y":0, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 255, + "priority": 1, + "focus":2 + }, + { + "x":-12, + "y":-20, + "zoomX":130, + "zoomY":120, + "visible":true, + "target": 2, + "graphicFrame": 16, + "opacity": 150, + "focus":2 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + } + ], + [ + { + "x":0, + "y":0, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":0, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":2 + }, + { + "x":128, + "y":-64, + "zoomX":100, + "zoomY":100, + "visible":true, + "target":1, + "graphicFrame":0, + "opacity": 255, + "locked": true, + "priority":1, + "focus":1 + } + ] + ], + "frameTimedEvents":{ + "16":[{"frameIndex":16,"resourceName":"PRSFX- Powder common1.wav","volume":100,"pitch":100,"eventType":"AnimTimedSoundEvent"}], + "18":[{"frameIndex":18,"resourceName":"PRSFX- Powder common2.wav","volume":30,"pitch":120,"eventType":"AnimTimedSoundEvent"}] + }, + "position":3, + "hue":0 +} \ No newline at end of file diff --git a/public/images/battle_anims/PRAS- Powder.png b/public/images/battle_anims/PRAS- Powder.png index 3c4a4d2db55..9656c31e6c9 100644 Binary files a/public/images/battle_anims/PRAS- Powder.png and b/public/images/battle_anims/PRAS- Powder.png differ diff --git a/public/images/items.png b/public/images/items.png index cb4f8fa7d06..191766f520e 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/catching_charm.png b/public/images/items/catching_charm.png index 9d72fe465e3..c220ff70c03 100644 Binary files a/public/images/items/catching_charm.png and b/public/images/items/catching_charm.png differ diff --git a/public/images/pokemon/1003.png b/public/images/pokemon/1003.png index 3a88f39d65f..eb69527246d 100644 Binary files a/public/images/pokemon/1003.png and b/public/images/pokemon/1003.png differ diff --git a/public/images/pokemon/159.png b/public/images/pokemon/159.png index 62de85e4ad7..46073ff539c 100644 Binary files a/public/images/pokemon/159.png and b/public/images/pokemon/159.png differ diff --git a/public/images/pokemon/164.png b/public/images/pokemon/164.png index c172959d338..b5d83ad6818 100644 Binary files a/public/images/pokemon/164.png and b/public/images/pokemon/164.png differ diff --git a/public/images/pokemon/190.png b/public/images/pokemon/190.png index b049ba83296..40563cec158 100644 Binary files a/public/images/pokemon/190.png and b/public/images/pokemon/190.png differ diff --git a/public/images/pokemon/199.png b/public/images/pokemon/199.png index d3b072497bd..f2e8888fb2d 100644 Binary files a/public/images/pokemon/199.png and b/public/images/pokemon/199.png differ diff --git a/public/images/pokemon/218.png b/public/images/pokemon/218.png index aeb9a35f9fe..64faad88ba3 100644 Binary files a/public/images/pokemon/218.png and b/public/images/pokemon/218.png differ diff --git a/public/images/pokemon/226.png b/public/images/pokemon/226.png index dbc04cc885f..006bd465b05 100644 Binary files a/public/images/pokemon/226.png and b/public/images/pokemon/226.png differ diff --git a/public/images/pokemon/228.png b/public/images/pokemon/228.png index c601cc4058d..476aa4d619d 100644 Binary files a/public/images/pokemon/228.png and b/public/images/pokemon/228.png differ diff --git a/public/images/pokemon/229-mega.png b/public/images/pokemon/229-mega.png index ebcda7bf90a..fac79bad8c1 100644 Binary files a/public/images/pokemon/229-mega.png and b/public/images/pokemon/229-mega.png differ diff --git a/public/images/pokemon/229.png b/public/images/pokemon/229.png index 270f36e64eb..6439012bacf 100644 Binary files a/public/images/pokemon/229.png and b/public/images/pokemon/229.png differ diff --git a/public/images/pokemon/232.png b/public/images/pokemon/232.png index ac7ae0eb5ae..0bfb76be5ed 100644 Binary files a/public/images/pokemon/232.png and b/public/images/pokemon/232.png differ diff --git a/public/images/pokemon/25-cool-cosplay.png b/public/images/pokemon/25-cool-cosplay.png index 123ae257598..ba1e1145901 100644 Binary files a/public/images/pokemon/25-cool-cosplay.png and b/public/images/pokemon/25-cool-cosplay.png differ diff --git a/public/images/pokemon/256.png b/public/images/pokemon/256.png index beb509fa9de..2a071e76188 100644 Binary files a/public/images/pokemon/256.png and b/public/images/pokemon/256.png differ diff --git a/public/images/pokemon/257-mega.png b/public/images/pokemon/257-mega.png index 0350e97deee..51d700c3e9a 100644 Binary files a/public/images/pokemon/257-mega.png and b/public/images/pokemon/257-mega.png differ diff --git a/public/images/pokemon/257.png b/public/images/pokemon/257.png index 76cfb0490e8..77dbc705a60 100644 Binary files a/public/images/pokemon/257.png and b/public/images/pokemon/257.png differ diff --git a/public/images/pokemon/261.png b/public/images/pokemon/261.png index a519b9951a4..9e47128a545 100644 Binary files a/public/images/pokemon/261.png and b/public/images/pokemon/261.png differ diff --git a/public/images/pokemon/262.png b/public/images/pokemon/262.png index ee95836e908..71509b88626 100644 Binary files a/public/images/pokemon/262.png and b/public/images/pokemon/262.png differ diff --git a/public/images/pokemon/308.png b/public/images/pokemon/308.png index 20e41507efa..94dd021f209 100644 Binary files a/public/images/pokemon/308.png and b/public/images/pokemon/308.png differ diff --git a/public/images/pokemon/335.png b/public/images/pokemon/335.png index 87f8b341b55..e5d051dd850 100644 Binary files a/public/images/pokemon/335.png and b/public/images/pokemon/335.png differ diff --git a/public/images/pokemon/336.png b/public/images/pokemon/336.png index 5908f2b36a2..daf64440220 100644 Binary files a/public/images/pokemon/336.png and b/public/images/pokemon/336.png differ diff --git a/public/images/pokemon/357.png b/public/images/pokemon/357.png index ce003f24c41..6b1ed63c3fe 100644 Binary files a/public/images/pokemon/357.png and b/public/images/pokemon/357.png differ diff --git a/public/images/pokemon/370.png b/public/images/pokemon/370.png index c53d42f9cd3..e9f29872fb2 100644 Binary files a/public/images/pokemon/370.png and b/public/images/pokemon/370.png differ diff --git a/public/images/pokemon/373.png b/public/images/pokemon/373.png index e3f967583a5..5174907b69b 100644 Binary files a/public/images/pokemon/373.png and b/public/images/pokemon/373.png differ diff --git a/public/images/pokemon/401.png b/public/images/pokemon/401.png index fa3dd915202..f4fffa67e54 100644 Binary files a/public/images/pokemon/401.png and b/public/images/pokemon/401.png differ diff --git a/public/images/pokemon/402.png b/public/images/pokemon/402.png index a7808e446a6..15de8f02a0d 100644 Binary files a/public/images/pokemon/402.png and b/public/images/pokemon/402.png differ diff --git a/public/images/pokemon/4080.png b/public/images/pokemon/4080.png index 4af9ba7ea0d..2ad2193cdbc 100644 Binary files a/public/images/pokemon/4080.png and b/public/images/pokemon/4080.png differ diff --git a/public/images/pokemon/418.png b/public/images/pokemon/418.png index 005c917463f..fba2402c2b3 100644 Binary files a/public/images/pokemon/418.png and b/public/images/pokemon/418.png differ diff --git a/public/images/pokemon/419.png b/public/images/pokemon/419.png index 572f819749b..5fbe90ff3bd 100644 Binary files a/public/images/pokemon/419.png and b/public/images/pokemon/419.png differ diff --git a/public/images/pokemon/4199.png b/public/images/pokemon/4199.png index df9dee11cdb..9c9f86e1f5d 100644 Binary files a/public/images/pokemon/4199.png and b/public/images/pokemon/4199.png differ diff --git a/public/images/pokemon/424.png b/public/images/pokemon/424.png index d19a5a79e4a..c0bc59a0caa 100644 Binary files a/public/images/pokemon/424.png and b/public/images/pokemon/424.png differ diff --git a/public/images/pokemon/45.png b/public/images/pokemon/45.png index db9c8d9c3f2..1c6f5a5a9f2 100644 Binary files a/public/images/pokemon/45.png and b/public/images/pokemon/45.png differ diff --git a/public/images/pokemon/451.json b/public/images/pokemon/451.json index 0e99c96f876..6c266f21a24 100644 --- a/public/images/pokemon/451.json +++ b/public/images/pokemon/451.json @@ -1,2330 +1,715 @@ -{ - "textures": [ - { - "image": "451.png", - "format": "RGBA8888", - "size": { - "w": 281, - "h": 281 - }, - "scale": 1, - "frames": [ - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 179, - "y": 87, - "w": 57, - "h": 45 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 179, - "y": 87, - "w": 57, - "h": 45 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 0, - "y": 129, - "w": 57, - "h": 45 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 0, - "y": 129, - "w": 57, - "h": 45 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 57, - "y": 132, - "w": 57, - "h": 45 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 57, - "y": 132, - "w": 57, - "h": 45 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 56, - "h": 45 - }, - "frame": { - "x": 114, - "y": 132, - "w": 56, - "h": 45 - } - }, - { - "filename": "0096.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 56, - "h": 45 - }, - "frame": { - "x": 114, - "y": 132, - "w": 56, - "h": 45 - } - }, - { - "filename": "0097.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 170, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0098.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 170, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0099.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 0, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 224, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0100.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 0, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 224, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0109.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 53, - "h": 46 - }, - "frame": { - "x": 228, - "y": 177, - "w": 53, - "h": 46 - } - }, - { - "filename": "0110.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 53, - "h": 46 - }, - "frame": { - "x": 228, - "y": 177, - "w": 53, - "h": 46 - } - }, - { - "filename": "0101.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 220, - "w": 54, - "h": 46 - } - }, - { - "filename": "0102.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 220, - "w": 54, - "h": 46 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0103.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 111, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0104.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 111, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0105.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 166, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0106.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 166, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0107.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 221, - "y": 223, - "w": 54, - "h": 46 - } - }, - { - "filename": "0108.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 221, - "y": 223, - "w": 54, - "h": 46 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:e303c68c1876b77078f3e1fd4372a4ce:84139d6b94cea0f3c45dbd8fa7109c3d:c79e17c206de27e3b7f1ce96f7df8e51$" - } -} +{ "frames": [ + { + "filename": "0055.png", + "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0001.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0002.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0003.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0004.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0005.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0007.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0008.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0009.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0010.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0011.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0012.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0013.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0014.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0015.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0016.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0017.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0018.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0019.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0020.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0021.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0022.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0023.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0024.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0025.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0026.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0027.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0028.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0029.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0030.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0031.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0032.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0033.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0034.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0035.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0036.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0037.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0038.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0039.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0040.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0041.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0042.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0043.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0044.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0045.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0046.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0047.png", + "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0048.png", + "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0049.png", + "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0050.png", + "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0051.png", + "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0052.png", + "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0053.png", + "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0054.png", + "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0056.png", + "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0057.png", + "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0058.png", + "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0059.png", + "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0060.png", + "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0061.png", + "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0062.png", + "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0063.png", + "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0064.png", + "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0065.png", + "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0066.png", + "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0067.png", + "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0068.png", + "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0069.png", + "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0070.png", + "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0071.png", + "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0072.png", + "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0073.png", + "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0074.png", + "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0075.png", + "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0076.png", + "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0077.png", + "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0078.png", + "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0079.png", + "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0080.png", + "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0081.png", + "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0082.png", + "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0083.png", + "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0084.png", + "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0085.png", + "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0086.png", + "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0087.png", + "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0088.png", + "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "451.png", + "format": "I8", + "size": { "w": 339, "h": 221 }, + "scale": "1" + } +} \ No newline at end of file diff --git a/public/images/pokemon/451.png b/public/images/pokemon/451.png index fac8f5a0170..716e8a08041 100644 Binary files a/public/images/pokemon/451.png and b/public/images/pokemon/451.png differ diff --git a/public/images/pokemon/456.png b/public/images/pokemon/456.png index 4b6c8c4e52f..501eb5904fe 100644 Binary files a/public/images/pokemon/456.png and b/public/images/pokemon/456.png differ diff --git a/public/images/pokemon/4562.png b/public/images/pokemon/4562.png index 1165444cc50..b08f724ab8b 100644 Binary files a/public/images/pokemon/4562.png and b/public/images/pokemon/4562.png differ diff --git a/public/images/pokemon/457.png b/public/images/pokemon/457.png index 6ee49243598..07425b2a147 100644 Binary files a/public/images/pokemon/457.png and b/public/images/pokemon/457.png differ diff --git a/public/images/pokemon/467.png b/public/images/pokemon/467.png index a22d045f925..ec7039932d1 100644 Binary files a/public/images/pokemon/467.png and b/public/images/pokemon/467.png differ diff --git a/public/images/pokemon/47.json b/public/images/pokemon/47.json index 437459a66e9..959bfe740f7 100644 --- a/public/images/pokemon/47.json +++ b/public/images/pokemon/47.json @@ -1,776 +1,299 @@ -{ - "textures": [ - { - "image": "47.png", - "format": "RGBA8888", - "size": { - "w": 230, - "h": 230 - }, - "scale": 1, - "frames": [ - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 12, - "w": 56, - "h": 49 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 49 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 12, - "w": 56, - "h": 49 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 49 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 12, - "w": 62, - "h": 51 - }, - "frame": { - "x": 56, - "y": 0, - "w": 62, - "h": 51 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 12, - "w": 62, - "h": 51 - }, - "frame": { - "x": 56, - "y": 0, - "w": 62, - "h": 51 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 8, - "w": 55, - "h": 53 - }, - "frame": { - "x": 118, - "y": 0, - "w": 55, - "h": 53 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 8, - "w": 55, - "h": 53 - }, - "frame": { - "x": 118, - "y": 0, - "w": 55, - "h": 53 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 55, - "h": 54 - }, - "frame": { - "x": 173, - "y": 0, - "w": 55, - "h": 54 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 55, - "h": 54 - }, - "frame": { - "x": 173, - "y": 0, - "w": 55, - "h": 54 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 53, - "h": 56 - }, - "frame": { - "x": 0, - "y": 49, - "w": 53, - "h": 56 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 53, - "h": 56 - }, - "frame": { - "x": 0, - "y": 49, - "w": 53, - "h": 56 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 62, - "h": 55 - }, - "frame": { - "x": 53, - "y": 51, - "w": 62, - "h": 55 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 62, - "h": 55 - }, - "frame": { - "x": 53, - "y": 51, - "w": 62, - "h": 55 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 53, - "h": 57 - }, - "frame": { - "x": 115, - "y": 53, - "w": 53, - "h": 57 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 53, - "h": 57 - }, - "frame": { - "x": 115, - "y": 53, - "w": 53, - "h": 57 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 62, - "h": 56 - }, - "frame": { - "x": 168, - "y": 54, - "w": 62, - "h": 56 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 62, - "h": 56 - }, - "frame": { - "x": 168, - "y": 54, - "w": 62, - "h": 56 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 61, - "h": 58 - }, - "frame": { - "x": 0, - "y": 106, - "w": 61, - "h": 58 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 61, - "h": 58 - }, - "frame": { - "x": 0, - "y": 106, - "w": 61, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 61, - "y": 106, - "w": 54, - "h": 59 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 61, - "y": 106, - "w": 54, - "h": 59 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 172, - "y": 110, - "w": 54, - "h": 59 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 172, - "y": 110, - "w": 54, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 61, - "h": 59 - }, - "frame": { - "x": 0, - "y": 164, - "w": 61, - "h": 59 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 61, - "h": 59 - }, - "frame": { - "x": 0, - "y": 164, - "w": 61, - "h": 59 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:b28fe643197bcc1def0e0ac2ba9f4e67:516d08c8e1ff13b49a109b082ef12860:fe45e2d628a6cef0908f7b82468c8798$" - } -} \ No newline at end of file +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0002.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0003.png", + "frame": { "x": 166, "y": 114, "w": 52, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0004.png", + "frame": { "x": 166, "y": 114, "w": 52, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0005.png", + "frame": { "x": 0, "y": 169, "w": 51, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 7, "w": 51, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 169, "w": 51, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 7, "w": 51, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0007.png", + "frame": { "x": 104, "y": 166, "w": 53, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 9, "w": 53, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0008.png", + "frame": { "x": 104, "y": 166, "w": 53, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 9, "w": 53, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0009.png", + "frame": { "x": 157, "y": 170, "w": 55, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 12, "w": 55, "h": 49 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0010.png", + "frame": { "x": 157, "y": 170, "w": 55, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 12, "w": 55, "h": 49 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0011.png", + "frame": { "x": 0, "y": 115, "w": 53, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 8, "w": 53, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0012.png", + "frame": { "x": 0, "y": 115, "w": 53, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 8, "w": 53, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0013.png", + "frame": { "x": 53, "y": 116, "w": 51, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 6, "w": 51, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0014.png", + "frame": { "x": 53, "y": 116, "w": 51, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 6, "w": 51, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0015.png", + "frame": { "x": 114, "y": 109, "w": 52, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0016.png", + "frame": { "x": 114, "y": 109, "w": 52, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0017.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0018.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0019.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0020.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0021.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0022.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0023.png", + "frame": { "x": 119, "y": 0, "w": 59, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 7, "w": 59, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0024.png", + "frame": { "x": 119, "y": 0, "w": 59, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 7, "w": 59, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0025.png", + "frame": { "x": 178, "y": 0, "w": 60, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 9, "w": 60, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0026.png", + "frame": { "x": 178, "y": 0, "w": 60, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 9, "w": 60, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0027.png", + "frame": { "x": 114, "y": 57, "w": 62, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 12, "w": 62, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0028.png", + "frame": { "x": 114, "y": 57, "w": 62, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 12, "w": 62, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0029.png", + "frame": { "x": 59, "y": 0, "w": 60, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 8, "w": 60, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0030.png", + "frame": { "x": 59, "y": 0, "w": 60, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 8, "w": 60, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0031.png", + "frame": { "x": 0, "y": 0, "w": 59, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 59, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0032.png", + "frame": { "x": 0, "y": 0, "w": 59, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 59, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0033.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0034.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0035.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0036.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "47.png", + "format": "I8", + "size": { "w": 238, "h": 223 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/47.png b/public/images/pokemon/47.png index f9474fda1c9..665cabf322e 100644 Binary files a/public/images/pokemon/47.png and b/public/images/pokemon/47.png differ diff --git a/public/images/pokemon/531-mega.png b/public/images/pokemon/531-mega.png index 4e692979402..f08cc04b51c 100644 Binary files a/public/images/pokemon/531-mega.png and b/public/images/pokemon/531-mega.png differ diff --git a/public/images/pokemon/669-blue.png b/public/images/pokemon/669-blue.png index 5cdefbbae6b..85426c58d51 100644 Binary files a/public/images/pokemon/669-blue.png and b/public/images/pokemon/669-blue.png differ diff --git a/public/images/pokemon/669-orange.png b/public/images/pokemon/669-orange.png index 6d93c5b13b8..565301b795b 100644 Binary files a/public/images/pokemon/669-orange.png and b/public/images/pokemon/669-orange.png differ diff --git a/public/images/pokemon/669-red.png b/public/images/pokemon/669-red.png index 02d89f59c31..b6219100729 100644 Binary files a/public/images/pokemon/669-red.png and b/public/images/pokemon/669-red.png differ diff --git a/public/images/pokemon/669-white.png b/public/images/pokemon/669-white.png index b815b86c5f5..a2809abf193 100644 Binary files a/public/images/pokemon/669-white.png and b/public/images/pokemon/669-white.png differ diff --git a/public/images/pokemon/669-yellow.png b/public/images/pokemon/669-yellow.png index 517406798e7..c3ad0eff812 100644 Binary files a/public/images/pokemon/669-yellow.png and b/public/images/pokemon/669-yellow.png differ diff --git a/public/images/pokemon/670-blue.png b/public/images/pokemon/670-blue.png index b70ec5532f9..4359006fe78 100644 Binary files a/public/images/pokemon/670-blue.png and b/public/images/pokemon/670-blue.png differ diff --git a/public/images/pokemon/670-orange.png b/public/images/pokemon/670-orange.png index 9beb7d884d5..d5c10c4c40b 100644 Binary files a/public/images/pokemon/670-orange.png and b/public/images/pokemon/670-orange.png differ diff --git a/public/images/pokemon/670-red.png b/public/images/pokemon/670-red.png index 9452ba90968..0ac18083828 100644 Binary files a/public/images/pokemon/670-red.png and b/public/images/pokemon/670-red.png differ diff --git a/public/images/pokemon/670-white.png b/public/images/pokemon/670-white.png index dba946ff47a..e8894d4f668 100644 Binary files a/public/images/pokemon/670-white.png and b/public/images/pokemon/670-white.png differ diff --git a/public/images/pokemon/670-yellow.png b/public/images/pokemon/670-yellow.png index 2d028fe5b1a..4803acafcb5 100644 Binary files a/public/images/pokemon/670-yellow.png and b/public/images/pokemon/670-yellow.png differ diff --git a/public/images/pokemon/671-blue.png b/public/images/pokemon/671-blue.png index ccbd88bb0b8..b384058bee9 100644 Binary files a/public/images/pokemon/671-blue.png and b/public/images/pokemon/671-blue.png differ diff --git a/public/images/pokemon/671-orange.png b/public/images/pokemon/671-orange.png index a99b8302e76..ac60adbcf10 100644 Binary files a/public/images/pokemon/671-orange.png and b/public/images/pokemon/671-orange.png differ diff --git a/public/images/pokemon/671-red.png b/public/images/pokemon/671-red.png index 460dafd4cc1..1b83ecf38ca 100644 Binary files a/public/images/pokemon/671-red.png and b/public/images/pokemon/671-red.png differ diff --git a/public/images/pokemon/671-white.png b/public/images/pokemon/671-white.png index 3d6f0d841b8..393970614d6 100644 Binary files a/public/images/pokemon/671-white.png and b/public/images/pokemon/671-white.png differ diff --git a/public/images/pokemon/671-yellow.png b/public/images/pokemon/671-yellow.png index 650e8db7a35..2af1b4aa543 100644 Binary files a/public/images/pokemon/671-yellow.png and b/public/images/pokemon/671-yellow.png differ diff --git a/public/images/pokemon/677.png b/public/images/pokemon/677.png index d0ebc245b32..3a5d6833ced 100644 Binary files a/public/images/pokemon/677.png and b/public/images/pokemon/677.png differ diff --git a/public/images/pokemon/678-female.png b/public/images/pokemon/678-female.png index 73abb68900a..087c5301dd0 100644 Binary files a/public/images/pokemon/678-female.png and b/public/images/pokemon/678-female.png differ diff --git a/public/images/pokemon/678.png b/public/images/pokemon/678.png index 7846bebd294..e4a1f8854fb 100644 Binary files a/public/images/pokemon/678.png and b/public/images/pokemon/678.png differ diff --git a/public/images/pokemon/698.png b/public/images/pokemon/698.png index 81c7b03e375..2e00f5693a6 100644 Binary files a/public/images/pokemon/698.png and b/public/images/pokemon/698.png differ diff --git a/public/images/pokemon/699.png b/public/images/pokemon/699.png index c8b7dda83ba..2d5320b71a2 100644 Binary files a/public/images/pokemon/699.png and b/public/images/pokemon/699.png differ diff --git a/public/images/pokemon/702.png b/public/images/pokemon/702.png index 0e3daf90889..4e0eccee204 100644 Binary files a/public/images/pokemon/702.png and b/public/images/pokemon/702.png differ diff --git a/public/images/pokemon/715.png b/public/images/pokemon/715.png index a13b8f435e4..e173bfe25ff 100644 Binary files a/public/images/pokemon/715.png and b/public/images/pokemon/715.png differ diff --git a/public/images/pokemon/716-active.png b/public/images/pokemon/716-active.png index 50b69e5ba7d..55ad63f4d3d 100644 Binary files a/public/images/pokemon/716-active.png and b/public/images/pokemon/716-active.png differ diff --git a/public/images/pokemon/716-neutral.png b/public/images/pokemon/716-neutral.png index 62a90a55dd1..8390dddffdc 100644 Binary files a/public/images/pokemon/716-neutral.png and b/public/images/pokemon/716-neutral.png differ diff --git a/public/images/pokemon/772.png b/public/images/pokemon/772.png index a32fd5c1b72..129e12d1e4c 100644 Binary files a/public/images/pokemon/772.png and b/public/images/pokemon/772.png differ diff --git a/public/images/pokemon/777.png b/public/images/pokemon/777.png index 074df4db673..bf1779d0f10 100644 Binary files a/public/images/pokemon/777.png and b/public/images/pokemon/777.png differ diff --git a/public/images/pokemon/778-disguised.png b/public/images/pokemon/778-disguised.png index 97397e9414c..b048cf02233 100644 Binary files a/public/images/pokemon/778-disguised.png and b/public/images/pokemon/778-disguised.png differ diff --git a/public/images/pokemon/80-mega.png b/public/images/pokemon/80-mega.png index 42aed7b3c66..6f7e9f0d962 100644 Binary files a/public/images/pokemon/80-mega.png and b/public/images/pokemon/80-mega.png differ diff --git a/public/images/pokemon/80.png b/public/images/pokemon/80.png index c6120f8b0b1..bcea1acb9b7 100644 Binary files a/public/images/pokemon/80.png and b/public/images/pokemon/80.png differ diff --git a/public/images/pokemon/818-gigantamax.png b/public/images/pokemon/818-gigantamax.png index c103073e4cf..c19add87a4f 100644 Binary files a/public/images/pokemon/818-gigantamax.png and b/public/images/pokemon/818-gigantamax.png differ diff --git a/public/images/pokemon/862.png b/public/images/pokemon/862.png index 4d6daf0a7da..3d82d7ef07a 100644 Binary files a/public/images/pokemon/862.png and b/public/images/pokemon/862.png differ diff --git a/public/images/pokemon/876-female.png b/public/images/pokemon/876-female.png index db4d869c148..3294cfc5b21 100644 Binary files a/public/images/pokemon/876-female.png and b/public/images/pokemon/876-female.png differ diff --git a/public/images/pokemon/876.png b/public/images/pokemon/876.png index a204d67325b..ae5a23d3d94 100644 Binary files a/public/images/pokemon/876.png and b/public/images/pokemon/876.png differ diff --git a/public/images/pokemon/881.png b/public/images/pokemon/881.png index 01cc12c00dd..321e2962b8b 100644 Binary files a/public/images/pokemon/881.png and b/public/images/pokemon/881.png differ diff --git a/public/images/pokemon/896.png b/public/images/pokemon/896.png index 1b64f2641a3..8157c737363 100644 Binary files a/public/images/pokemon/896.png and b/public/images/pokemon/896.png differ diff --git a/public/images/pokemon/898-ice.png b/public/images/pokemon/898-ice.png index 3723a78f33a..ebbe798f696 100644 Binary files a/public/images/pokemon/898-ice.png and b/public/images/pokemon/898-ice.png differ diff --git a/public/images/pokemon/898-shadow.png b/public/images/pokemon/898-shadow.png index 30bdbe224ec..cf6e79c1794 100644 Binary files a/public/images/pokemon/898-shadow.png and b/public/images/pokemon/898-shadow.png differ diff --git a/public/images/pokemon/898.png b/public/images/pokemon/898.png index d20e1f937e1..6966777e821 100644 Binary files a/public/images/pokemon/898.png and b/public/images/pokemon/898.png differ diff --git a/public/images/pokemon/912.png b/public/images/pokemon/912.png index 60b288d350e..022a653212c 100644 Binary files a/public/images/pokemon/912.png and b/public/images/pokemon/912.png differ diff --git a/public/images/pokemon/913.png b/public/images/pokemon/913.png index 7baef852807..328b48ff47b 100644 Binary files a/public/images/pokemon/913.png and b/public/images/pokemon/913.png differ diff --git a/public/images/pokemon/914.png b/public/images/pokemon/914.png index a2a89748c90..4e044dfae23 100644 Binary files a/public/images/pokemon/914.png and b/public/images/pokemon/914.png differ diff --git a/public/images/pokemon/940.png b/public/images/pokemon/940.png index f35482bb36a..434afc4a180 100644 Binary files a/public/images/pokemon/940.png and b/public/images/pokemon/940.png differ diff --git a/public/images/pokemon/941.png b/public/images/pokemon/941.png index 417d7923296..bbe885fd7b1 100644 Binary files a/public/images/pokemon/941.png and b/public/images/pokemon/941.png differ diff --git a/public/images/pokemon/981.png b/public/images/pokemon/981.png index ff66259c58d..b0f4a16498e 100644 Binary files a/public/images/pokemon/981.png and b/public/images/pokemon/981.png differ diff --git a/public/images/pokemon/997.png b/public/images/pokemon/997.png index 6b9248d85a3..5fee59edad6 100644 Binary files a/public/images/pokemon/997.png and b/public/images/pokemon/997.png differ diff --git a/public/images/pokemon/back/1003.png b/public/images/pokemon/back/1003.png index 77ece80dcc9..36ee14788b3 100644 Binary files a/public/images/pokemon/back/1003.png and b/public/images/pokemon/back/1003.png differ diff --git a/public/images/pokemon/back/126.png b/public/images/pokemon/back/126.png index 4b65d1323c8..193c38f7e2c 100644 Binary files a/public/images/pokemon/back/126.png and b/public/images/pokemon/back/126.png differ diff --git a/public/images/pokemon/back/159.png b/public/images/pokemon/back/159.png index 710014bf180..699661bb510 100644 Binary files a/public/images/pokemon/back/159.png and b/public/images/pokemon/back/159.png differ diff --git a/public/images/pokemon/back/190.png b/public/images/pokemon/back/190.png index bc4a5eee405..687eb29a67f 100644 Binary files a/public/images/pokemon/back/190.png and b/public/images/pokemon/back/190.png differ diff --git a/public/images/pokemon/back/199.png b/public/images/pokemon/back/199.png index 099dffb2a61..a7cffbc7820 100644 Binary files a/public/images/pokemon/back/199.png and b/public/images/pokemon/back/199.png differ diff --git a/public/images/pokemon/back/228.png b/public/images/pokemon/back/228.png index e723a44c31e..3a1952f81e7 100644 Binary files a/public/images/pokemon/back/228.png and b/public/images/pokemon/back/228.png differ diff --git a/public/images/pokemon/back/229-mega.png b/public/images/pokemon/back/229-mega.png index 6e09a3ae218..2c13a17ccc1 100644 Binary files a/public/images/pokemon/back/229-mega.png and b/public/images/pokemon/back/229-mega.png differ diff --git a/public/images/pokemon/back/229.png b/public/images/pokemon/back/229.png index 6cdef1e4d79..08692a9babe 100644 Binary files a/public/images/pokemon/back/229.png and b/public/images/pokemon/back/229.png differ diff --git a/public/images/pokemon/back/25-gigantamax.png b/public/images/pokemon/back/25-gigantamax.png index c80b3d5d486..1bed545d749 100644 Binary files a/public/images/pokemon/back/25-gigantamax.png and b/public/images/pokemon/back/25-gigantamax.png differ diff --git a/public/images/pokemon/back/256.png b/public/images/pokemon/back/256.png index 86f771dee3f..9e58466f2ba 100644 Binary files a/public/images/pokemon/back/256.png and b/public/images/pokemon/back/256.png differ diff --git a/public/images/pokemon/back/257-mega.png b/public/images/pokemon/back/257-mega.png index 1222c1cf8e2..df1f65d5811 100644 Binary files a/public/images/pokemon/back/257-mega.png and b/public/images/pokemon/back/257-mega.png differ diff --git a/public/images/pokemon/back/257.png b/public/images/pokemon/back/257.png index cc319ea4841..0104c4799da 100644 Binary files a/public/images/pokemon/back/257.png and b/public/images/pokemon/back/257.png differ diff --git a/public/images/pokemon/back/261.png b/public/images/pokemon/back/261.png index ce8d9579465..b44a6dc019e 100644 Binary files a/public/images/pokemon/back/261.png and b/public/images/pokemon/back/261.png differ diff --git a/public/images/pokemon/back/262.png b/public/images/pokemon/back/262.png index e1c7a26e2e8..2bc9bf59816 100644 Binary files a/public/images/pokemon/back/262.png and b/public/images/pokemon/back/262.png differ diff --git a/public/images/pokemon/back/303-mega.png b/public/images/pokemon/back/303-mega.png index c1fe85b22ff..94960dc84b1 100644 Binary files a/public/images/pokemon/back/303-mega.png and b/public/images/pokemon/back/303-mega.png differ diff --git a/public/images/pokemon/back/357.png b/public/images/pokemon/back/357.png index 4c832d552a9..7a7e551c4a2 100644 Binary files a/public/images/pokemon/back/357.png and b/public/images/pokemon/back/357.png differ diff --git a/public/images/pokemon/back/401.png b/public/images/pokemon/back/401.png index 61baacef0b6..1227e0533e3 100644 Binary files a/public/images/pokemon/back/401.png and b/public/images/pokemon/back/401.png differ diff --git a/public/images/pokemon/back/402.png b/public/images/pokemon/back/402.png index 68d74ba524d..f74de4e8dc2 100644 Binary files a/public/images/pokemon/back/402.png and b/public/images/pokemon/back/402.png differ diff --git a/public/images/pokemon/back/4080.png b/public/images/pokemon/back/4080.png index ce3c9cbd3f5..182424bd1e0 100644 Binary files a/public/images/pokemon/back/4080.png and b/public/images/pokemon/back/4080.png differ diff --git a/public/images/pokemon/back/412-sandy.png b/public/images/pokemon/back/412-sandy.png index c76152b11bc..61219669129 100644 Binary files a/public/images/pokemon/back/412-sandy.png and b/public/images/pokemon/back/412-sandy.png differ diff --git a/public/images/pokemon/back/418.png b/public/images/pokemon/back/418.png index 5d2cf6c1368..724948d9809 100644 Binary files a/public/images/pokemon/back/418.png and b/public/images/pokemon/back/418.png differ diff --git a/public/images/pokemon/back/419.png b/public/images/pokemon/back/419.png index 6ece4983987..7a389c2cbe3 100644 Binary files a/public/images/pokemon/back/419.png and b/public/images/pokemon/back/419.png differ diff --git a/public/images/pokemon/back/4199.png b/public/images/pokemon/back/4199.png index 1b45a0c2016..eb02986212f 100644 Binary files a/public/images/pokemon/back/4199.png and b/public/images/pokemon/back/4199.png differ diff --git a/public/images/pokemon/back/424.png b/public/images/pokemon/back/424.png index 76bc69d20e8..c953801431e 100644 Binary files a/public/images/pokemon/back/424.png and b/public/images/pokemon/back/424.png differ diff --git a/public/images/pokemon/back/456.png b/public/images/pokemon/back/456.png index 74d21eee031..216cca54757 100644 Binary files a/public/images/pokemon/back/456.png and b/public/images/pokemon/back/456.png differ diff --git a/public/images/pokemon/back/4562.png b/public/images/pokemon/back/4562.png index 6bbbf07ebdc..1e23be42716 100644 Binary files a/public/images/pokemon/back/4562.png and b/public/images/pokemon/back/4562.png differ diff --git a/public/images/pokemon/back/457.png b/public/images/pokemon/back/457.png index 239b235e7bd..806cacfaed2 100644 Binary files a/public/images/pokemon/back/457.png and b/public/images/pokemon/back/457.png differ diff --git a/public/images/pokemon/back/467.png b/public/images/pokemon/back/467.png index 0073f964ade..5edba20e48e 100644 Binary files a/public/images/pokemon/back/467.png and b/public/images/pokemon/back/467.png differ diff --git a/public/images/pokemon/back/556.png b/public/images/pokemon/back/556.png index aeab696307a..43652a17d06 100644 Binary files a/public/images/pokemon/back/556.png and b/public/images/pokemon/back/556.png differ diff --git a/public/images/pokemon/back/569-gigantamax.png b/public/images/pokemon/back/569-gigantamax.png index ca4e897ecc2..334c513dd73 100644 Binary files a/public/images/pokemon/back/569-gigantamax.png and b/public/images/pokemon/back/569-gigantamax.png differ diff --git a/public/images/pokemon/back/699.png b/public/images/pokemon/back/699.png index d24d2ad0a73..25693e4a3f9 100644 Binary files a/public/images/pokemon/back/699.png and b/public/images/pokemon/back/699.png differ diff --git a/public/images/pokemon/back/702.png b/public/images/pokemon/back/702.png index 9b375837be3..2b1d490b71a 100644 Binary files a/public/images/pokemon/back/702.png and b/public/images/pokemon/back/702.png differ diff --git a/public/images/pokemon/back/716-active.png b/public/images/pokemon/back/716-active.png index 0b5d2ec3c66..c3276ec81b6 100644 Binary files a/public/images/pokemon/back/716-active.png and b/public/images/pokemon/back/716-active.png differ diff --git a/public/images/pokemon/back/716-neutral.png b/public/images/pokemon/back/716-neutral.png index 291fb1600dd..73ebb3231cc 100644 Binary files a/public/images/pokemon/back/716-neutral.png and b/public/images/pokemon/back/716-neutral.png differ diff --git a/public/images/pokemon/back/772.png b/public/images/pokemon/back/772.png index eb4e5e9b8b1..059671261d7 100644 Binary files a/public/images/pokemon/back/772.png and b/public/images/pokemon/back/772.png differ diff --git a/public/images/pokemon/back/798.png b/public/images/pokemon/back/798.png index 085db6e0876..262f0b4fd79 100644 Binary files a/public/images/pokemon/back/798.png and b/public/images/pokemon/back/798.png differ diff --git a/public/images/pokemon/back/80-mega.png b/public/images/pokemon/back/80-mega.png index 5bc773e3475..005e2a813f3 100644 Binary files a/public/images/pokemon/back/80-mega.png and b/public/images/pokemon/back/80-mega.png differ diff --git a/public/images/pokemon/back/80.png b/public/images/pokemon/back/80.png index 25b7f5fff9c..f993d4b695b 100644 Binary files a/public/images/pokemon/back/80.png and b/public/images/pokemon/back/80.png differ diff --git a/public/images/pokemon/back/818-gigantamax.png b/public/images/pokemon/back/818-gigantamax.png index 93dc06b038c..22b6e71281a 100644 Binary files a/public/images/pokemon/back/818-gigantamax.png and b/public/images/pokemon/back/818-gigantamax.png differ diff --git a/public/images/pokemon/back/876-female.png b/public/images/pokemon/back/876-female.png index 29e7d4a2afd..5c25cc2f977 100644 Binary files a/public/images/pokemon/back/876-female.png and b/public/images/pokemon/back/876-female.png differ diff --git a/public/images/pokemon/back/876.png b/public/images/pokemon/back/876.png index 1ba33ec5516..1fc71387c62 100644 Binary files a/public/images/pokemon/back/876.png and b/public/images/pokemon/back/876.png differ diff --git a/public/images/pokemon/back/880.png b/public/images/pokemon/back/880.png index a82239996bc..d175b340bb8 100644 Binary files a/public/images/pokemon/back/880.png and b/public/images/pokemon/back/880.png differ diff --git a/public/images/pokemon/back/881.png b/public/images/pokemon/back/881.png index 852453aaaa0..2f77327cfd0 100644 Binary files a/public/images/pokemon/back/881.png and b/public/images/pokemon/back/881.png differ diff --git a/public/images/pokemon/back/882.png b/public/images/pokemon/back/882.png index cb05d6c5a91..0675270f6e9 100644 Binary files a/public/images/pokemon/back/882.png and b/public/images/pokemon/back/882.png differ diff --git a/public/images/pokemon/back/896.png b/public/images/pokemon/back/896.png index 243e9a4cfde..f989d532c8b 100644 Binary files a/public/images/pokemon/back/896.png and b/public/images/pokemon/back/896.png differ diff --git a/public/images/pokemon/back/898-ice.png b/public/images/pokemon/back/898-ice.png index ca5b032669e..b279a7416ed 100644 Binary files a/public/images/pokemon/back/898-ice.png and b/public/images/pokemon/back/898-ice.png differ diff --git a/public/images/pokemon/back/898-shadow.png b/public/images/pokemon/back/898-shadow.png index 0bbf0d2083c..0491ba74c86 100644 Binary files a/public/images/pokemon/back/898-shadow.png and b/public/images/pokemon/back/898-shadow.png differ diff --git a/public/images/pokemon/back/898.png b/public/images/pokemon/back/898.png index f99aa938ea2..fafc2947908 100644 Binary files a/public/images/pokemon/back/898.png and b/public/images/pokemon/back/898.png differ diff --git a/public/images/pokemon/back/913.png b/public/images/pokemon/back/913.png index 0935def3821..4a08acae4ea 100644 Binary files a/public/images/pokemon/back/913.png and b/public/images/pokemon/back/913.png differ diff --git a/public/images/pokemon/back/914.png b/public/images/pokemon/back/914.png index 7e87e0cc60e..221a91d018a 100644 Binary files a/public/images/pokemon/back/914.png and b/public/images/pokemon/back/914.png differ diff --git a/public/images/pokemon/back/981.png b/public/images/pokemon/back/981.png index 38b1a715f69..6744ddcf6b9 100644 Binary files a/public/images/pokemon/back/981.png and b/public/images/pokemon/back/981.png differ diff --git a/public/images/pokemon/back/female/190.png b/public/images/pokemon/back/female/190.png index 68b0624d213..1028c6410a6 100644 Binary files a/public/images/pokemon/back/female/190.png and b/public/images/pokemon/back/female/190.png differ diff --git a/public/images/pokemon/back/female/229.png b/public/images/pokemon/back/female/229.png index 76be7104638..f3bb6b6f2f6 100644 Binary files a/public/images/pokemon/back/female/229.png and b/public/images/pokemon/back/female/229.png differ diff --git a/public/images/pokemon/back/female/256.png b/public/images/pokemon/back/female/256.png index 6b6f7b97b7c..92612d4ccdc 100644 Binary files a/public/images/pokemon/back/female/256.png and b/public/images/pokemon/back/female/256.png differ diff --git a/public/images/pokemon/back/female/257.png b/public/images/pokemon/back/female/257.png index 0b93d07d99e..7b8171f39c0 100644 Binary files a/public/images/pokemon/back/female/257.png and b/public/images/pokemon/back/female/257.png differ diff --git a/public/images/pokemon/back/female/401.png b/public/images/pokemon/back/female/401.png index 2b0206a6707..8ebce5ff353 100644 Binary files a/public/images/pokemon/back/female/401.png and b/public/images/pokemon/back/female/401.png differ diff --git a/public/images/pokemon/back/female/402.png b/public/images/pokemon/back/female/402.png index bc8ca92ea94..6e88b492759 100644 Binary files a/public/images/pokemon/back/female/402.png and b/public/images/pokemon/back/female/402.png differ diff --git a/public/images/pokemon/back/female/418.png b/public/images/pokemon/back/female/418.png index 14c1eee1a63..1fc721cd534 100644 Binary files a/public/images/pokemon/back/female/418.png and b/public/images/pokemon/back/female/418.png differ diff --git a/public/images/pokemon/back/female/419.png b/public/images/pokemon/back/female/419.png index 9c757d2faba..22ac91c7208 100644 Binary files a/public/images/pokemon/back/female/419.png and b/public/images/pokemon/back/female/419.png differ diff --git a/public/images/pokemon/back/female/424.png b/public/images/pokemon/back/female/424.png index b05fbcf8a19..b8128ba481b 100644 Binary files a/public/images/pokemon/back/female/424.png and b/public/images/pokemon/back/female/424.png differ diff --git a/public/images/pokemon/back/female/456.png b/public/images/pokemon/back/female/456.png index dd326cb32f1..4c666b8cc19 100644 Binary files a/public/images/pokemon/back/female/456.png and b/public/images/pokemon/back/female/456.png differ diff --git a/public/images/pokemon/back/female/457.png b/public/images/pokemon/back/female/457.png index 04e1ae12ff4..9f243381edc 100644 Binary files a/public/images/pokemon/back/female/457.png and b/public/images/pokemon/back/female/457.png differ diff --git a/public/images/pokemon/back/shiny/1003.png b/public/images/pokemon/back/shiny/1003.png index 6ebce339091..fa50a1c8ffe 100644 Binary files a/public/images/pokemon/back/shiny/1003.png and b/public/images/pokemon/back/shiny/1003.png differ diff --git a/public/images/pokemon/back/shiny/190.png b/public/images/pokemon/back/shiny/190.png index cdbf2611180..a0a65c18c7f 100644 Binary files a/public/images/pokemon/back/shiny/190.png and b/public/images/pokemon/back/shiny/190.png differ diff --git a/public/images/pokemon/back/shiny/257-mega.png b/public/images/pokemon/back/shiny/257-mega.png index 40eb7a61bb5..04603c4a5e0 100644 Binary files a/public/images/pokemon/back/shiny/257-mega.png and b/public/images/pokemon/back/shiny/257-mega.png differ diff --git a/public/images/pokemon/back/shiny/261.png b/public/images/pokemon/back/shiny/261.png index a43f3d73f76..3399736c315 100644 Binary files a/public/images/pokemon/back/shiny/261.png and b/public/images/pokemon/back/shiny/261.png differ diff --git a/public/images/pokemon/back/shiny/262.png b/public/images/pokemon/back/shiny/262.png index afcc2c8bfe6..9e76bfed3fc 100644 Binary files a/public/images/pokemon/back/shiny/262.png and b/public/images/pokemon/back/shiny/262.png differ diff --git a/public/images/pokemon/back/shiny/303-mega.png b/public/images/pokemon/back/shiny/303-mega.png index c7de009cf2a..7097e527ddf 100644 Binary files a/public/images/pokemon/back/shiny/303-mega.png and b/public/images/pokemon/back/shiny/303-mega.png differ diff --git a/public/images/pokemon/back/shiny/357.png b/public/images/pokemon/back/shiny/357.png index da2497041e4..783f4401b2d 100644 Binary files a/public/images/pokemon/back/shiny/357.png and b/public/images/pokemon/back/shiny/357.png differ diff --git a/public/images/pokemon/back/shiny/373-mega.png b/public/images/pokemon/back/shiny/373-mega.png index 95145fd40e3..ce2c48a77b4 100644 Binary files a/public/images/pokemon/back/shiny/373-mega.png and b/public/images/pokemon/back/shiny/373-mega.png differ diff --git a/public/images/pokemon/back/shiny/4080.png b/public/images/pokemon/back/shiny/4080.png index 2c8d85fa5d7..4f214c399b8 100644 Binary files a/public/images/pokemon/back/shiny/4080.png and b/public/images/pokemon/back/shiny/4080.png differ diff --git a/public/images/pokemon/back/shiny/424.png b/public/images/pokemon/back/shiny/424.png index 67675146686..7e27173b989 100644 Binary files a/public/images/pokemon/back/shiny/424.png and b/public/images/pokemon/back/shiny/424.png differ diff --git a/public/images/pokemon/back/shiny/433.png b/public/images/pokemon/back/shiny/433.png index a6bb0e59098..040b700225f 100644 Binary files a/public/images/pokemon/back/shiny/433.png and b/public/images/pokemon/back/shiny/433.png differ diff --git a/public/images/pokemon/back/shiny/469.png b/public/images/pokemon/back/shiny/469.png index 8ac1f32335d..bdd46a1ed16 100644 Binary files a/public/images/pokemon/back/shiny/469.png and b/public/images/pokemon/back/shiny/469.png differ diff --git a/public/images/pokemon/back/shiny/477.png b/public/images/pokemon/back/shiny/477.png index 25414ab5c18..45879a61d11 100644 Binary files a/public/images/pokemon/back/shiny/477.png and b/public/images/pokemon/back/shiny/477.png differ diff --git a/public/images/pokemon/back/shiny/556.png b/public/images/pokemon/back/shiny/556.png index 4f2e991b010..8a5a573e5d5 100644 Binary files a/public/images/pokemon/back/shiny/556.png and b/public/images/pokemon/back/shiny/556.png differ diff --git a/public/images/pokemon/back/shiny/698.png b/public/images/pokemon/back/shiny/698.png index 22f9b2b660d..3daad1fd4ab 100644 Binary files a/public/images/pokemon/back/shiny/698.png and b/public/images/pokemon/back/shiny/698.png differ diff --git a/public/images/pokemon/back/shiny/699.png b/public/images/pokemon/back/shiny/699.png index 58bd840f1e3..0eb93c80f38 100644 Binary files a/public/images/pokemon/back/shiny/699.png and b/public/images/pokemon/back/shiny/699.png differ diff --git a/public/images/pokemon/back/shiny/716-active.png b/public/images/pokemon/back/shiny/716-active.png index 7c4db546443..96c6814d109 100644 Binary files a/public/images/pokemon/back/shiny/716-active.png and b/public/images/pokemon/back/shiny/716-active.png differ diff --git a/public/images/pokemon/back/shiny/716-neutral.png b/public/images/pokemon/back/shiny/716-neutral.png index fe52ce43e0c..9c80656e93c 100644 Binary files a/public/images/pokemon/back/shiny/716-neutral.png and b/public/images/pokemon/back/shiny/716-neutral.png differ diff --git a/public/images/pokemon/back/shiny/772.png b/public/images/pokemon/back/shiny/772.png index f999ba8ec01..e275c06ee41 100644 Binary files a/public/images/pokemon/back/shiny/772.png and b/public/images/pokemon/back/shiny/772.png differ diff --git a/public/images/pokemon/back/shiny/773.png b/public/images/pokemon/back/shiny/773.png index 6dba183d9d2..26aafa2d2ea 100644 Binary files a/public/images/pokemon/back/shiny/773.png and b/public/images/pokemon/back/shiny/773.png differ diff --git a/public/images/pokemon/back/shiny/798.png b/public/images/pokemon/back/shiny/798.png index b1948a6a3cd..56a45c895a0 100644 Binary files a/public/images/pokemon/back/shiny/798.png and b/public/images/pokemon/back/shiny/798.png differ diff --git a/public/images/pokemon/back/shiny/80-mega.png b/public/images/pokemon/back/shiny/80-mega.png index 6c508034c3e..5baf6ccb873 100644 Binary files a/public/images/pokemon/back/shiny/80-mega.png and b/public/images/pokemon/back/shiny/80-mega.png differ diff --git a/public/images/pokemon/back/shiny/80.png b/public/images/pokemon/back/shiny/80.png index 68e3d526965..c6c3b23f364 100644 Binary files a/public/images/pokemon/back/shiny/80.png and b/public/images/pokemon/back/shiny/80.png differ diff --git a/public/images/pokemon/back/shiny/818-gigantamax.png b/public/images/pokemon/back/shiny/818-gigantamax.png index 224858fc0be..ca58c4eb34c 100644 Binary files a/public/images/pokemon/back/shiny/818-gigantamax.png and b/public/images/pokemon/back/shiny/818-gigantamax.png differ diff --git a/public/images/pokemon/back/shiny/898-ice.png b/public/images/pokemon/back/shiny/898-ice.png index 3bf247280b9..0e96f7ca9d3 100644 Binary files a/public/images/pokemon/back/shiny/898-ice.png and b/public/images/pokemon/back/shiny/898-ice.png differ diff --git a/public/images/pokemon/back/shiny/898-shadow.png b/public/images/pokemon/back/shiny/898-shadow.png index a1ea98d1f3f..6319b0c56d1 100644 Binary files a/public/images/pokemon/back/shiny/898-shadow.png and b/public/images/pokemon/back/shiny/898-shadow.png differ diff --git a/public/images/pokemon/back/shiny/913.png b/public/images/pokemon/back/shiny/913.png index d57246f3565..e3a4405fd1e 100644 Binary files a/public/images/pokemon/back/shiny/913.png and b/public/images/pokemon/back/shiny/913.png differ diff --git a/public/images/pokemon/back/shiny/914.png b/public/images/pokemon/back/shiny/914.png index f22947be030..5e88e004266 100644 Binary files a/public/images/pokemon/back/shiny/914.png and b/public/images/pokemon/back/shiny/914.png differ diff --git a/public/images/pokemon/back/shiny/981.png b/public/images/pokemon/back/shiny/981.png index b89fa6b1629..0a5d6e5fb90 100644 Binary files a/public/images/pokemon/back/shiny/981.png and b/public/images/pokemon/back/shiny/981.png differ diff --git a/public/images/pokemon/back/shiny/983.png b/public/images/pokemon/back/shiny/983.png index 4b69919c297..5cdf12cc859 100644 Binary files a/public/images/pokemon/back/shiny/983.png and b/public/images/pokemon/back/shiny/983.png differ diff --git a/public/images/pokemon/back/shiny/female/190.png b/public/images/pokemon/back/shiny/female/190.png index 11c7f6a84e5..cb2352cfb4d 100644 Binary files a/public/images/pokemon/back/shiny/female/190.png and b/public/images/pokemon/back/shiny/female/190.png differ diff --git a/public/images/pokemon/back/shiny/female/424.png b/public/images/pokemon/back/shiny/female/424.png index dd620a0cfa9..a00c99e375e 100644 Binary files a/public/images/pokemon/back/shiny/female/424.png and b/public/images/pokemon/back/shiny/female/424.png differ diff --git a/public/images/pokemon/exp/1003.png b/public/images/pokemon/exp/1003.png index e6c8cbc2b67..2d02c4050e9 100644 Binary files a/public/images/pokemon/exp/1003.png and b/public/images/pokemon/exp/1003.png differ diff --git a/public/images/pokemon/exp/229-mega.png b/public/images/pokemon/exp/229-mega.png index bc7656d9f1c..babc5310bb4 100644 Binary files a/public/images/pokemon/exp/229-mega.png and b/public/images/pokemon/exp/229-mega.png differ diff --git a/public/images/pokemon/exp/373-mega.png b/public/images/pokemon/exp/373-mega.png index 93c27d88db6..0b93c472c76 100644 Binary files a/public/images/pokemon/exp/373-mega.png and b/public/images/pokemon/exp/373-mega.png differ diff --git a/public/images/pokemon/exp/4080.png b/public/images/pokemon/exp/4080.png index 81890a8b9e5..77e47856b0a 100644 Binary files a/public/images/pokemon/exp/4080.png and b/public/images/pokemon/exp/4080.png differ diff --git a/public/images/pokemon/exp/4199.png b/public/images/pokemon/exp/4199.png index b1858760c0c..b7a7dd9bfbb 100644 Binary files a/public/images/pokemon/exp/4199.png and b/public/images/pokemon/exp/4199.png differ diff --git a/public/images/pokemon/exp/4222.png b/public/images/pokemon/exp/4222.png index b78d04c371c..5e04aafdc22 100644 Binary files a/public/images/pokemon/exp/4222.png and b/public/images/pokemon/exp/4222.png differ diff --git a/public/images/pokemon/exp/4264.png b/public/images/pokemon/exp/4264.png index 3436fe2fe13..4bc70f2fdce 100644 Binary files a/public/images/pokemon/exp/4264.png and b/public/images/pokemon/exp/4264.png differ diff --git a/public/images/pokemon/exp/4562.png b/public/images/pokemon/exp/4562.png index 69b90f182ca..307390e8e2c 100644 Binary files a/public/images/pokemon/exp/4562.png and b/public/images/pokemon/exp/4562.png differ diff --git a/public/images/pokemon/exp/531-mega.png b/public/images/pokemon/exp/531-mega.png index d9b93c2ecca..391cc22b6db 100644 Binary files a/public/images/pokemon/exp/531-mega.png and b/public/images/pokemon/exp/531-mega.png differ diff --git a/public/images/pokemon/exp/677.png b/public/images/pokemon/exp/677.png index 70d8a0f185d..d25f1675102 100644 Binary files a/public/images/pokemon/exp/677.png and b/public/images/pokemon/exp/677.png differ diff --git a/public/images/pokemon/exp/678-female.png b/public/images/pokemon/exp/678-female.png index 3af30f5d2a7..b2021bd5923 100644 Binary files a/public/images/pokemon/exp/678-female.png and b/public/images/pokemon/exp/678-female.png differ diff --git a/public/images/pokemon/exp/698.png b/public/images/pokemon/exp/698.png index 3fba3dad32a..6d8ca555881 100644 Binary files a/public/images/pokemon/exp/698.png and b/public/images/pokemon/exp/698.png differ diff --git a/public/images/pokemon/exp/699.png b/public/images/pokemon/exp/699.png index d2108e813aa..1b7a4f402e3 100644 Binary files a/public/images/pokemon/exp/699.png and b/public/images/pokemon/exp/699.png differ diff --git a/public/images/pokemon/exp/702.png b/public/images/pokemon/exp/702.png index a5d375e779a..9d042fd8293 100644 Binary files a/public/images/pokemon/exp/702.png and b/public/images/pokemon/exp/702.png differ diff --git a/public/images/pokemon/exp/716-active.png b/public/images/pokemon/exp/716-active.png index 2f6ec8f76da..223a73d9cb7 100644 Binary files a/public/images/pokemon/exp/716-active.png and b/public/images/pokemon/exp/716-active.png differ diff --git a/public/images/pokemon/exp/716-neutral.png b/public/images/pokemon/exp/716-neutral.png index dd2d1821986..91fefde1eb7 100644 Binary files a/public/images/pokemon/exp/716-neutral.png and b/public/images/pokemon/exp/716-neutral.png differ diff --git a/public/images/pokemon/exp/752.png b/public/images/pokemon/exp/752.png index 88bbba63252..e78e9803919 100644 Binary files a/public/images/pokemon/exp/752.png and b/public/images/pokemon/exp/752.png differ diff --git a/public/images/pokemon/exp/770.png b/public/images/pokemon/exp/770.png index 7cfd1179298..30f9073a58f 100644 Binary files a/public/images/pokemon/exp/770.png and b/public/images/pokemon/exp/770.png differ diff --git a/public/images/pokemon/exp/771.png b/public/images/pokemon/exp/771.png index d53f1e52286..3a2ef676382 100644 Binary files a/public/images/pokemon/exp/771.png and b/public/images/pokemon/exp/771.png differ diff --git a/public/images/pokemon/exp/772.png b/public/images/pokemon/exp/772.png index c3a986ff15e..0a28235f416 100644 Binary files a/public/images/pokemon/exp/772.png and b/public/images/pokemon/exp/772.png differ diff --git a/public/images/pokemon/exp/773_Test.png b/public/images/pokemon/exp/773_Test.png index a77cdf691e2..ab932e78c1a 100644 Binary files a/public/images/pokemon/exp/773_Test.png and b/public/images/pokemon/exp/773_Test.png differ diff --git a/public/images/pokemon/exp/776.png b/public/images/pokemon/exp/776.png index f002de4cf6a..07550683210 100644 Binary files a/public/images/pokemon/exp/776.png and b/public/images/pokemon/exp/776.png differ diff --git a/public/images/pokemon/exp/777.png b/public/images/pokemon/exp/777.png index 0683a9e0855..f60fcc50786 100644 Binary files a/public/images/pokemon/exp/777.png and b/public/images/pokemon/exp/777.png differ diff --git a/public/images/pokemon/exp/793.png b/public/images/pokemon/exp/793.png index 683dd1153f1..fb1e37da979 100644 Binary files a/public/images/pokemon/exp/793.png and b/public/images/pokemon/exp/793.png differ diff --git a/public/images/pokemon/exp/798.png b/public/images/pokemon/exp/798.png index 6c88f1130c9..8434a1721e8 100644 Binary files a/public/images/pokemon/exp/798.png and b/public/images/pokemon/exp/798.png differ diff --git a/public/images/pokemon/exp/818.png b/public/images/pokemon/exp/818.png index ff767c962a4..b71cccf2679 100644 Binary files a/public/images/pokemon/exp/818.png and b/public/images/pokemon/exp/818.png differ diff --git a/public/images/pokemon/exp/857.png b/public/images/pokemon/exp/857.png index 0a3195e2bda..85f17ece3d8 100644 Binary files a/public/images/pokemon/exp/857.png and b/public/images/pokemon/exp/857.png differ diff --git a/public/images/pokemon/exp/876-female.png b/public/images/pokemon/exp/876-female.png index eb61b85e351..0eb5b407970 100644 Binary files a/public/images/pokemon/exp/876-female.png and b/public/images/pokemon/exp/876-female.png differ diff --git a/public/images/pokemon/exp/876.png b/public/images/pokemon/exp/876.png index e1c47e16c0c..842a98830a7 100644 Binary files a/public/images/pokemon/exp/876.png and b/public/images/pokemon/exp/876.png differ diff --git a/public/images/pokemon/exp/880.png b/public/images/pokemon/exp/880.png index 7edffc97cc7..34a898fb5ac 100644 Binary files a/public/images/pokemon/exp/880.png and b/public/images/pokemon/exp/880.png differ diff --git a/public/images/pokemon/exp/881.png b/public/images/pokemon/exp/881.png index aeaa1728f1a..291f4c261fa 100644 Binary files a/public/images/pokemon/exp/881.png and b/public/images/pokemon/exp/881.png differ diff --git a/public/images/pokemon/exp/882.png b/public/images/pokemon/exp/882.png index 15269a499ca..37c4facc672 100644 Binary files a/public/images/pokemon/exp/882.png and b/public/images/pokemon/exp/882.png differ diff --git a/public/images/pokemon/exp/896.png b/public/images/pokemon/exp/896.png index f6f646aec65..7025ae49fb8 100644 Binary files a/public/images/pokemon/exp/896.png and b/public/images/pokemon/exp/896.png differ diff --git a/public/images/pokemon/exp/898-ice.png b/public/images/pokemon/exp/898-ice.png index c7c00f5551d..3cdf6adc202 100644 Binary files a/public/images/pokemon/exp/898-ice.png and b/public/images/pokemon/exp/898-ice.png differ diff --git a/public/images/pokemon/exp/898-shadow.png b/public/images/pokemon/exp/898-shadow.png index 5a41eb6cee0..b5f18487f32 100644 Binary files a/public/images/pokemon/exp/898-shadow.png and b/public/images/pokemon/exp/898-shadow.png differ diff --git a/public/images/pokemon/exp/912.png b/public/images/pokemon/exp/912.png index 23c03342039..b8c7465768c 100644 Binary files a/public/images/pokemon/exp/912.png and b/public/images/pokemon/exp/912.png differ diff --git a/public/images/pokemon/exp/913.png b/public/images/pokemon/exp/913.png index 1bfd1c8f022..538f852208a 100644 Binary files a/public/images/pokemon/exp/913.png and b/public/images/pokemon/exp/913.png differ diff --git a/public/images/pokemon/exp/914.png b/public/images/pokemon/exp/914.png index 33ca7719da2..013934d03f5 100644 Binary files a/public/images/pokemon/exp/914.png and b/public/images/pokemon/exp/914.png differ diff --git a/public/images/pokemon/exp/940.png b/public/images/pokemon/exp/940.png index 42aed88443c..5ecc20ea5d1 100644 Binary files a/public/images/pokemon/exp/940.png and b/public/images/pokemon/exp/940.png differ diff --git a/public/images/pokemon/exp/954.png b/public/images/pokemon/exp/954.png index 6097a0f0393..85a6017c417 100644 Binary files a/public/images/pokemon/exp/954.png and b/public/images/pokemon/exp/954.png differ diff --git a/public/images/pokemon/exp/974.png b/public/images/pokemon/exp/974.png index e016f114cce..354bcbed533 100644 Binary files a/public/images/pokemon/exp/974.png and b/public/images/pokemon/exp/974.png differ diff --git a/public/images/pokemon/exp/981.png b/public/images/pokemon/exp/981.png index 5c87495f6e9..c763e8ec358 100644 Binary files a/public/images/pokemon/exp/981.png and b/public/images/pokemon/exp/981.png differ diff --git a/public/images/pokemon/exp/983.png b/public/images/pokemon/exp/983.png index 226d971ae32..2fac1759c12 100644 Binary files a/public/images/pokemon/exp/983.png and b/public/images/pokemon/exp/983.png differ diff --git a/public/images/pokemon/exp/back/1003.png b/public/images/pokemon/exp/back/1003.png index d83789d1fb8..fc368e56f81 100644 Binary files a/public/images/pokemon/exp/back/1003.png and b/public/images/pokemon/exp/back/1003.png differ diff --git a/public/images/pokemon/exp/back/229-mega.png b/public/images/pokemon/exp/back/229-mega.png index aae59a3a1d0..b538f3523dd 100644 Binary files a/public/images/pokemon/exp/back/229-mega.png and b/public/images/pokemon/exp/back/229-mega.png differ diff --git a/public/images/pokemon/exp/back/257-mega.png b/public/images/pokemon/exp/back/257-mega.png index 48bd446329f..b27b98fa153 100644 Binary files a/public/images/pokemon/exp/back/257-mega.png and b/public/images/pokemon/exp/back/257-mega.png differ diff --git a/public/images/pokemon/exp/back/4080.png b/public/images/pokemon/exp/back/4080.png index f4cfb4cd3c5..d0ffefd6503 100644 Binary files a/public/images/pokemon/exp/back/4080.png and b/public/images/pokemon/exp/back/4080.png differ diff --git a/public/images/pokemon/exp/back/4199.png b/public/images/pokemon/exp/back/4199.png index 174a325b28a..aa841a9e4d1 100644 Binary files a/public/images/pokemon/exp/back/4199.png and b/public/images/pokemon/exp/back/4199.png differ diff --git a/public/images/pokemon/exp/back/4222.png b/public/images/pokemon/exp/back/4222.png index 202817b2163..163dcb1432e 100644 Binary files a/public/images/pokemon/exp/back/4222.png and b/public/images/pokemon/exp/back/4222.png differ diff --git a/public/images/pokemon/exp/back/4562.png b/public/images/pokemon/exp/back/4562.png index 691c60c8b0a..0f8fdde8b81 100644 Binary files a/public/images/pokemon/exp/back/4562.png and b/public/images/pokemon/exp/back/4562.png differ diff --git a/public/images/pokemon/exp/back/698.png b/public/images/pokemon/exp/back/698.png index 6250dc0110f..43738b78928 100644 Binary files a/public/images/pokemon/exp/back/698.png and b/public/images/pokemon/exp/back/698.png differ diff --git a/public/images/pokemon/exp/back/699.png b/public/images/pokemon/exp/back/699.png index 7558357e206..cd1b9a82de7 100644 Binary files a/public/images/pokemon/exp/back/699.png and b/public/images/pokemon/exp/back/699.png differ diff --git a/public/images/pokemon/exp/back/702.png b/public/images/pokemon/exp/back/702.png index be5cf42476b..e90d2e5200b 100644 Binary files a/public/images/pokemon/exp/back/702.png and b/public/images/pokemon/exp/back/702.png differ diff --git a/public/images/pokemon/exp/back/716-active.png b/public/images/pokemon/exp/back/716-active.png index 5bd4fcd74c6..9d10446def7 100644 Binary files a/public/images/pokemon/exp/back/716-active.png and b/public/images/pokemon/exp/back/716-active.png differ diff --git a/public/images/pokemon/exp/back/716-neutral.png b/public/images/pokemon/exp/back/716-neutral.png index 579173b757f..871a9471357 100644 Binary files a/public/images/pokemon/exp/back/716-neutral.png and b/public/images/pokemon/exp/back/716-neutral.png differ diff --git a/public/images/pokemon/exp/back/772.png b/public/images/pokemon/exp/back/772.png index b287c20b1cb..b1ccc79fb6c 100644 Binary files a/public/images/pokemon/exp/back/772.png and b/public/images/pokemon/exp/back/772.png differ diff --git a/public/images/pokemon/exp/back/776.png b/public/images/pokemon/exp/back/776.png index 6e0c7026d56..d23a85dc55d 100644 Binary files a/public/images/pokemon/exp/back/776.png and b/public/images/pokemon/exp/back/776.png differ diff --git a/public/images/pokemon/exp/back/777.png b/public/images/pokemon/exp/back/777.png index d8f25cc7362..f4b0e23c5e2 100644 Binary files a/public/images/pokemon/exp/back/777.png and b/public/images/pokemon/exp/back/777.png differ diff --git a/public/images/pokemon/exp/back/797.png b/public/images/pokemon/exp/back/797.png index 17cae929dbc..2c3258b6858 100644 Binary files a/public/images/pokemon/exp/back/797.png and b/public/images/pokemon/exp/back/797.png differ diff --git a/public/images/pokemon/exp/back/798.png b/public/images/pokemon/exp/back/798.png index 50b663a19dd..1cc3b7c4103 100644 Binary files a/public/images/pokemon/exp/back/798.png and b/public/images/pokemon/exp/back/798.png differ diff --git a/public/images/pokemon/exp/back/80-mega.png b/public/images/pokemon/exp/back/80-mega.png index f6c67586eaa..a8bcfbcff97 100644 Binary files a/public/images/pokemon/exp/back/80-mega.png and b/public/images/pokemon/exp/back/80-mega.png differ diff --git a/public/images/pokemon/exp/back/804.png b/public/images/pokemon/exp/back/804.png index 1dbf3c502d3..5de6c92f36d 100644 Binary files a/public/images/pokemon/exp/back/804.png and b/public/images/pokemon/exp/back/804.png differ diff --git a/public/images/pokemon/exp/back/876-female.png b/public/images/pokemon/exp/back/876-female.png index 78ca08e6d09..18a4a9834a9 100644 Binary files a/public/images/pokemon/exp/back/876-female.png and b/public/images/pokemon/exp/back/876-female.png differ diff --git a/public/images/pokemon/exp/back/876.png b/public/images/pokemon/exp/back/876.png index 0073e1642af..87ed3646b3e 100644 Binary files a/public/images/pokemon/exp/back/876.png and b/public/images/pokemon/exp/back/876.png differ diff --git a/public/images/pokemon/exp/back/880.png b/public/images/pokemon/exp/back/880.png index 4ee65833e55..c2891b4165a 100644 Binary files a/public/images/pokemon/exp/back/880.png and b/public/images/pokemon/exp/back/880.png differ diff --git a/public/images/pokemon/exp/back/881.png b/public/images/pokemon/exp/back/881.png index 377347f49bc..1c0322a43fd 100644 Binary files a/public/images/pokemon/exp/back/881.png and b/public/images/pokemon/exp/back/881.png differ diff --git a/public/images/pokemon/exp/back/882.png b/public/images/pokemon/exp/back/882.png index c2c9b1b302b..d5d5ad902f5 100644 Binary files a/public/images/pokemon/exp/back/882.png and b/public/images/pokemon/exp/back/882.png differ diff --git a/public/images/pokemon/exp/back/888-crowned.png b/public/images/pokemon/exp/back/888-crowned.png index 4cc454caaa1..827678bf53d 100644 Binary files a/public/images/pokemon/exp/back/888-crowned.png and b/public/images/pokemon/exp/back/888-crowned.png differ diff --git a/public/images/pokemon/exp/back/896.png b/public/images/pokemon/exp/back/896.png index dd7a984cae7..c0c26abd888 100644 Binary files a/public/images/pokemon/exp/back/896.png and b/public/images/pokemon/exp/back/896.png differ diff --git a/public/images/pokemon/exp/back/898-ice.png b/public/images/pokemon/exp/back/898-ice.png index 899a4dd0896..bbb27761875 100644 Binary files a/public/images/pokemon/exp/back/898-ice.png and b/public/images/pokemon/exp/back/898-ice.png differ diff --git a/public/images/pokemon/exp/back/898-shadow.png b/public/images/pokemon/exp/back/898-shadow.png index 9f40e0ede70..27191a9428b 100644 Binary files a/public/images/pokemon/exp/back/898-shadow.png and b/public/images/pokemon/exp/back/898-shadow.png differ diff --git a/public/images/pokemon/exp/back/898.png b/public/images/pokemon/exp/back/898.png index eb7bc938ee3..c2154aa45e9 100644 Binary files a/public/images/pokemon/exp/back/898.png and b/public/images/pokemon/exp/back/898.png differ diff --git a/public/images/pokemon/exp/back/913.png b/public/images/pokemon/exp/back/913.png index 9a33a6de3af..cd32c18afcc 100644 Binary files a/public/images/pokemon/exp/back/913.png and b/public/images/pokemon/exp/back/913.png differ diff --git a/public/images/pokemon/exp/back/914.png b/public/images/pokemon/exp/back/914.png index abf275e24f0..e476a2e58ec 100644 Binary files a/public/images/pokemon/exp/back/914.png and b/public/images/pokemon/exp/back/914.png differ diff --git a/public/images/pokemon/exp/back/954.png b/public/images/pokemon/exp/back/954.png index be75ebbb453..64a60ce7b89 100644 Binary files a/public/images/pokemon/exp/back/954.png and b/public/images/pokemon/exp/back/954.png differ diff --git a/public/images/pokemon/exp/back/981.png b/public/images/pokemon/exp/back/981.png index 360f4267be5..523135b7f3c 100644 Binary files a/public/images/pokemon/exp/back/981.png and b/public/images/pokemon/exp/back/981.png differ diff --git a/public/images/pokemon/exp/back/shiny/1003.png b/public/images/pokemon/exp/back/shiny/1003.png index 628a1098ab6..503211f914a 100644 Binary files a/public/images/pokemon/exp/back/shiny/1003.png and b/public/images/pokemon/exp/back/shiny/1003.png differ diff --git a/public/images/pokemon/exp/back/shiny/257-mega.png b/public/images/pokemon/exp/back/shiny/257-mega.png index 47725c71073..1aaad61acbf 100644 Binary files a/public/images/pokemon/exp/back/shiny/257-mega.png and b/public/images/pokemon/exp/back/shiny/257-mega.png differ diff --git a/public/images/pokemon/exp/back/shiny/373-mega.png b/public/images/pokemon/exp/back/shiny/373-mega.png index 6c5ce34d155..558a7ef2fbc 100644 Binary files a/public/images/pokemon/exp/back/shiny/373-mega.png and b/public/images/pokemon/exp/back/shiny/373-mega.png differ diff --git a/public/images/pokemon/exp/back/shiny/4080.png b/public/images/pokemon/exp/back/shiny/4080.png index e70503a7820..1f3ec6c122a 100644 Binary files a/public/images/pokemon/exp/back/shiny/4080.png and b/public/images/pokemon/exp/back/shiny/4080.png differ diff --git a/public/images/pokemon/exp/back/shiny/4222.png b/public/images/pokemon/exp/back/shiny/4222.png index 15970729f49..e5c30260c2b 100644 Binary files a/public/images/pokemon/exp/back/shiny/4222.png and b/public/images/pokemon/exp/back/shiny/4222.png differ diff --git a/public/images/pokemon/exp/back/shiny/698.png b/public/images/pokemon/exp/back/shiny/698.png index 781f363220a..dbd5cefcbf0 100644 Binary files a/public/images/pokemon/exp/back/shiny/698.png and b/public/images/pokemon/exp/back/shiny/698.png differ diff --git a/public/images/pokemon/exp/back/shiny/699.png b/public/images/pokemon/exp/back/shiny/699.png index 0a17af2f53f..75f5c8d8b78 100644 Binary files a/public/images/pokemon/exp/back/shiny/699.png and b/public/images/pokemon/exp/back/shiny/699.png differ diff --git a/public/images/pokemon/exp/back/shiny/708.png b/public/images/pokemon/exp/back/shiny/708.png index 8673e917e75..35e2594241a 100644 Binary files a/public/images/pokemon/exp/back/shiny/708.png and b/public/images/pokemon/exp/back/shiny/708.png differ diff --git a/public/images/pokemon/exp/back/shiny/709.png b/public/images/pokemon/exp/back/shiny/709.png index 5148cd1ed08..c0d67915d17 100644 Binary files a/public/images/pokemon/exp/back/shiny/709.png and b/public/images/pokemon/exp/back/shiny/709.png differ diff --git a/public/images/pokemon/exp/back/shiny/716-active.png b/public/images/pokemon/exp/back/shiny/716-active.png index b99aabd1496..effe84542dc 100644 Binary files a/public/images/pokemon/exp/back/shiny/716-active.png and b/public/images/pokemon/exp/back/shiny/716-active.png differ diff --git a/public/images/pokemon/exp/back/shiny/716-neutral.png b/public/images/pokemon/exp/back/shiny/716-neutral.png index c8bee30f32f..26ee61454fd 100644 Binary files a/public/images/pokemon/exp/back/shiny/716-neutral.png and b/public/images/pokemon/exp/back/shiny/716-neutral.png differ diff --git a/public/images/pokemon/exp/back/shiny/772.png b/public/images/pokemon/exp/back/shiny/772.png index 407e3be6691..49124763f6c 100644 Binary files a/public/images/pokemon/exp/back/shiny/772.png and b/public/images/pokemon/exp/back/shiny/772.png differ diff --git a/public/images/pokemon/exp/back/shiny/773.png b/public/images/pokemon/exp/back/shiny/773.png index f6a98c1b43a..2c4741a6b0b 100644 Binary files a/public/images/pokemon/exp/back/shiny/773.png and b/public/images/pokemon/exp/back/shiny/773.png differ diff --git a/public/images/pokemon/exp/back/shiny/776.png b/public/images/pokemon/exp/back/shiny/776.png index d669b9f3ce6..4c4115a8132 100644 Binary files a/public/images/pokemon/exp/back/shiny/776.png and b/public/images/pokemon/exp/back/shiny/776.png differ diff --git a/public/images/pokemon/exp/back/shiny/778-disguised.png b/public/images/pokemon/exp/back/shiny/778-disguised.png index 2b1c9ecf3c9..8d3183cee0c 100644 Binary files a/public/images/pokemon/exp/back/shiny/778-disguised.png and b/public/images/pokemon/exp/back/shiny/778-disguised.png differ diff --git a/public/images/pokemon/exp/back/shiny/798.png b/public/images/pokemon/exp/back/shiny/798.png index 68fd3382ddc..322600a1fdf 100644 Binary files a/public/images/pokemon/exp/back/shiny/798.png and b/public/images/pokemon/exp/back/shiny/798.png differ diff --git a/public/images/pokemon/exp/back/shiny/80-mega.png b/public/images/pokemon/exp/back/shiny/80-mega.png index 12e88cdb937..af2277cb9da 100644 Binary files a/public/images/pokemon/exp/back/shiny/80-mega.png and b/public/images/pokemon/exp/back/shiny/80-mega.png differ diff --git a/public/images/pokemon/exp/back/shiny/888-crowned.png b/public/images/pokemon/exp/back/shiny/888-crowned.png index b3eb012e3c8..a4ed4c67ed4 100644 Binary files a/public/images/pokemon/exp/back/shiny/888-crowned.png and b/public/images/pokemon/exp/back/shiny/888-crowned.png differ diff --git a/public/images/pokemon/exp/back/shiny/898-ice.png b/public/images/pokemon/exp/back/shiny/898-ice.png index 6b3e8277180..9f1a5bd03e3 100644 Binary files a/public/images/pokemon/exp/back/shiny/898-ice.png and b/public/images/pokemon/exp/back/shiny/898-ice.png differ diff --git a/public/images/pokemon/exp/back/shiny/898-shadow.png b/public/images/pokemon/exp/back/shiny/898-shadow.png index d40bd1ab48e..2a506193e7d 100644 Binary files a/public/images/pokemon/exp/back/shiny/898-shadow.png and b/public/images/pokemon/exp/back/shiny/898-shadow.png differ diff --git a/public/images/pokemon/exp/back/shiny/913.png b/public/images/pokemon/exp/back/shiny/913.png index 07838b5d996..9d65e55e94a 100644 Binary files a/public/images/pokemon/exp/back/shiny/913.png and b/public/images/pokemon/exp/back/shiny/913.png differ diff --git a/public/images/pokemon/exp/back/shiny/914.png b/public/images/pokemon/exp/back/shiny/914.png index 2f0510312bb..17f787add8f 100644 Binary files a/public/images/pokemon/exp/back/shiny/914.png and b/public/images/pokemon/exp/back/shiny/914.png differ diff --git a/public/images/pokemon/exp/back/shiny/954.png b/public/images/pokemon/exp/back/shiny/954.png index 3d2111bd99e..e12b479d462 100644 Binary files a/public/images/pokemon/exp/back/shiny/954.png and b/public/images/pokemon/exp/back/shiny/954.png differ diff --git a/public/images/pokemon/exp/back/shiny/981.png b/public/images/pokemon/exp/back/shiny/981.png index 90a25d7eca3..153f6594be4 100644 Binary files a/public/images/pokemon/exp/back/shiny/981.png and b/public/images/pokemon/exp/back/shiny/981.png differ diff --git a/public/images/pokemon/exp/shiny/1003.png b/public/images/pokemon/exp/shiny/1003.png index 6fab9bfe143..22c1d89a879 100644 Binary files a/public/images/pokemon/exp/shiny/1003.png and b/public/images/pokemon/exp/shiny/1003.png differ diff --git a/public/images/pokemon/exp/shiny/373-mega.png b/public/images/pokemon/exp/shiny/373-mega.png index dc42bd43632..15f7a2e9017 100644 Binary files a/public/images/pokemon/exp/shiny/373-mega.png and b/public/images/pokemon/exp/shiny/373-mega.png differ diff --git a/public/images/pokemon/exp/shiny/4080.png b/public/images/pokemon/exp/shiny/4080.png index 9c383118928..20cf5abe061 100644 Binary files a/public/images/pokemon/exp/shiny/4080.png and b/public/images/pokemon/exp/shiny/4080.png differ diff --git a/public/images/pokemon/exp/shiny/4222.png b/public/images/pokemon/exp/shiny/4222.png index 443aeb5fab3..62ab7974e7e 100644 Binary files a/public/images/pokemon/exp/shiny/4222.png and b/public/images/pokemon/exp/shiny/4222.png differ diff --git a/public/images/pokemon/exp/shiny/4264.png b/public/images/pokemon/exp/shiny/4264.png index 4848f512a60..3508177e683 100644 Binary files a/public/images/pokemon/exp/shiny/4264.png and b/public/images/pokemon/exp/shiny/4264.png differ diff --git a/public/images/pokemon/exp/shiny/677.png b/public/images/pokemon/exp/shiny/677.png index d2703b7f7c0..1a236f65716 100644 Binary files a/public/images/pokemon/exp/shiny/677.png and b/public/images/pokemon/exp/shiny/677.png differ diff --git a/public/images/pokemon/exp/shiny/678-female.png b/public/images/pokemon/exp/shiny/678-female.png index 7ae67d63a33..021055baf2c 100644 Binary files a/public/images/pokemon/exp/shiny/678-female.png and b/public/images/pokemon/exp/shiny/678-female.png differ diff --git a/public/images/pokemon/exp/shiny/679.png b/public/images/pokemon/exp/shiny/679.png index 6ac3c47f498..46373f60ead 100644 Binary files a/public/images/pokemon/exp/shiny/679.png and b/public/images/pokemon/exp/shiny/679.png differ diff --git a/public/images/pokemon/exp/shiny/698.png b/public/images/pokemon/exp/shiny/698.png index 322a44b92e8..84d8ba3dac5 100644 Binary files a/public/images/pokemon/exp/shiny/698.png and b/public/images/pokemon/exp/shiny/698.png differ diff --git a/public/images/pokemon/exp/shiny/699.png b/public/images/pokemon/exp/shiny/699.png index d68bd5c0e2a..7ec9dbd7252 100644 Binary files a/public/images/pokemon/exp/shiny/699.png and b/public/images/pokemon/exp/shiny/699.png differ diff --git a/public/images/pokemon/exp/shiny/716-active.png b/public/images/pokemon/exp/shiny/716-active.png index 898d23dd4a4..126c1a78e79 100644 Binary files a/public/images/pokemon/exp/shiny/716-active.png and b/public/images/pokemon/exp/shiny/716-active.png differ diff --git a/public/images/pokemon/exp/shiny/716-neutral.png b/public/images/pokemon/exp/shiny/716-neutral.png index 062deb9ba6a..4806e57123b 100644 Binary files a/public/images/pokemon/exp/shiny/716-neutral.png and b/public/images/pokemon/exp/shiny/716-neutral.png differ diff --git a/public/images/pokemon/exp/shiny/752.png b/public/images/pokemon/exp/shiny/752.png index 0902846f5f5..22a70b76218 100644 Binary files a/public/images/pokemon/exp/shiny/752.png and b/public/images/pokemon/exp/shiny/752.png differ diff --git a/public/images/pokemon/exp/shiny/771.png b/public/images/pokemon/exp/shiny/771.png index 76bf27514d4..b9e8e315b77 100644 Binary files a/public/images/pokemon/exp/shiny/771.png and b/public/images/pokemon/exp/shiny/771.png differ diff --git a/public/images/pokemon/exp/shiny/772.png b/public/images/pokemon/exp/shiny/772.png index 80be3267ea8..16bc67faf12 100644 Binary files a/public/images/pokemon/exp/shiny/772.png and b/public/images/pokemon/exp/shiny/772.png differ diff --git a/public/images/pokemon/exp/shiny/773.png b/public/images/pokemon/exp/shiny/773.png index 407cc9144ef..9b0c6b98b9d 100644 Binary files a/public/images/pokemon/exp/shiny/773.png and b/public/images/pokemon/exp/shiny/773.png differ diff --git a/public/images/pokemon/exp/shiny/776.png b/public/images/pokemon/exp/shiny/776.png index 882a4ed5a6d..a0a363aff3e 100644 Binary files a/public/images/pokemon/exp/shiny/776.png and b/public/images/pokemon/exp/shiny/776.png differ diff --git a/public/images/pokemon/exp/shiny/777.png b/public/images/pokemon/exp/shiny/777.png index 4862152e407..504a2d23abc 100644 Binary files a/public/images/pokemon/exp/shiny/777.png and b/public/images/pokemon/exp/shiny/777.png differ diff --git a/public/images/pokemon/exp/shiny/778-disguised.png b/public/images/pokemon/exp/shiny/778-disguised.png index 13fecc069c6..07f0b9183c9 100644 Binary files a/public/images/pokemon/exp/shiny/778-disguised.png and b/public/images/pokemon/exp/shiny/778-disguised.png differ diff --git a/public/images/pokemon/exp/shiny/793.png b/public/images/pokemon/exp/shiny/793.png index 7cc43d73f02..72a1e65037a 100644 Binary files a/public/images/pokemon/exp/shiny/793.png and b/public/images/pokemon/exp/shiny/793.png differ diff --git a/public/images/pokemon/exp/shiny/798.png b/public/images/pokemon/exp/shiny/798.png index b88b0c1efa5..d67c0a50fdc 100644 Binary files a/public/images/pokemon/exp/shiny/798.png and b/public/images/pokemon/exp/shiny/798.png differ diff --git a/public/images/pokemon/exp/shiny/818.png b/public/images/pokemon/exp/shiny/818.png index f4a8bfa2f6a..0105e7e38ab 100644 Binary files a/public/images/pokemon/exp/shiny/818.png and b/public/images/pokemon/exp/shiny/818.png differ diff --git a/public/images/pokemon/exp/shiny/857.png b/public/images/pokemon/exp/shiny/857.png index 65a7c0f8f39..331e54f93e6 100644 Binary files a/public/images/pokemon/exp/shiny/857.png and b/public/images/pokemon/exp/shiny/857.png differ diff --git a/public/images/pokemon/exp/shiny/898-ice.png b/public/images/pokemon/exp/shiny/898-ice.png index 33850906ca3..4fc9cb48083 100644 Binary files a/public/images/pokemon/exp/shiny/898-ice.png and b/public/images/pokemon/exp/shiny/898-ice.png differ diff --git a/public/images/pokemon/exp/shiny/898-shadow.png b/public/images/pokemon/exp/shiny/898-shadow.png index 8c4e18e6ba8..8587106d483 100644 Binary files a/public/images/pokemon/exp/shiny/898-shadow.png and b/public/images/pokemon/exp/shiny/898-shadow.png differ diff --git a/public/images/pokemon/exp/shiny/912.png b/public/images/pokemon/exp/shiny/912.png index 1f1692d6ceb..9306f0d8cef 100644 Binary files a/public/images/pokemon/exp/shiny/912.png and b/public/images/pokemon/exp/shiny/912.png differ diff --git a/public/images/pokemon/exp/shiny/913.png b/public/images/pokemon/exp/shiny/913.png index 7e458a0796c..9179cfe07f7 100644 Binary files a/public/images/pokemon/exp/shiny/913.png and b/public/images/pokemon/exp/shiny/913.png differ diff --git a/public/images/pokemon/exp/shiny/914.png b/public/images/pokemon/exp/shiny/914.png index bffa5a1489d..8697f0ea3fd 100644 Binary files a/public/images/pokemon/exp/shiny/914.png and b/public/images/pokemon/exp/shiny/914.png differ diff --git a/public/images/pokemon/exp/shiny/940.png b/public/images/pokemon/exp/shiny/940.png index 2e66b03cd9f..7eab3973ebf 100644 Binary files a/public/images/pokemon/exp/shiny/940.png and b/public/images/pokemon/exp/shiny/940.png differ diff --git a/public/images/pokemon/exp/shiny/954.png b/public/images/pokemon/exp/shiny/954.png index 8bb46149716..87f5093f221 100644 Binary files a/public/images/pokemon/exp/shiny/954.png and b/public/images/pokemon/exp/shiny/954.png differ diff --git a/public/images/pokemon/exp/shiny/970.png b/public/images/pokemon/exp/shiny/970.png index 52ad602b0a4..d455d836e51 100644 Binary files a/public/images/pokemon/exp/shiny/970.png and b/public/images/pokemon/exp/shiny/970.png differ diff --git a/public/images/pokemon/exp/shiny/981.png b/public/images/pokemon/exp/shiny/981.png index f8f977fed92..5cced7d6d3f 100644 Binary files a/public/images/pokemon/exp/shiny/981.png and b/public/images/pokemon/exp/shiny/981.png differ diff --git a/public/images/pokemon/exp/shiny/983.png b/public/images/pokemon/exp/shiny/983.png index 36e0df81562..f7505c9b1a6 100644 Binary files a/public/images/pokemon/exp/shiny/983.png and b/public/images/pokemon/exp/shiny/983.png differ diff --git a/public/images/pokemon/female/190.png b/public/images/pokemon/female/190.png index d0d5a1a2f76..a2d5c0b7b7d 100644 Binary files a/public/images/pokemon/female/190.png and b/public/images/pokemon/female/190.png differ diff --git a/public/images/pokemon/female/229.png b/public/images/pokemon/female/229.png index 3d0b516a797..836ceab6bb5 100644 Binary files a/public/images/pokemon/female/229.png and b/public/images/pokemon/female/229.png differ diff --git a/public/images/pokemon/female/232.png b/public/images/pokemon/female/232.png index 0359febee15..9477fd192f7 100644 Binary files a/public/images/pokemon/female/232.png and b/public/images/pokemon/female/232.png differ diff --git a/public/images/pokemon/female/25-cool-cosplay.png b/public/images/pokemon/female/25-cool-cosplay.png index ae1e7c76d44..9f76c7826f1 100644 Binary files a/public/images/pokemon/female/25-cool-cosplay.png and b/public/images/pokemon/female/25-cool-cosplay.png differ diff --git a/public/images/pokemon/female/256.png b/public/images/pokemon/female/256.png index f470f5daf4b..c8b0b2cbd4c 100644 Binary files a/public/images/pokemon/female/256.png and b/public/images/pokemon/female/256.png differ diff --git a/public/images/pokemon/female/257.png b/public/images/pokemon/female/257.png index 1c8a4c70757..ed3e8d2914e 100644 Binary files a/public/images/pokemon/female/257.png and b/public/images/pokemon/female/257.png differ diff --git a/public/images/pokemon/female/401.png b/public/images/pokemon/female/401.png index 8e145b0c1c2..4cd3fc77097 100644 Binary files a/public/images/pokemon/female/401.png and b/public/images/pokemon/female/401.png differ diff --git a/public/images/pokemon/female/402.png b/public/images/pokemon/female/402.png index bfa3b5b8670..1b92a7a9a95 100644 Binary files a/public/images/pokemon/female/402.png and b/public/images/pokemon/female/402.png differ diff --git a/public/images/pokemon/female/418.png b/public/images/pokemon/female/418.png index 005c917463f..fba2402c2b3 100644 Binary files a/public/images/pokemon/female/418.png and b/public/images/pokemon/female/418.png differ diff --git a/public/images/pokemon/female/419.png b/public/images/pokemon/female/419.png index 572f819749b..5fbe90ff3bd 100644 Binary files a/public/images/pokemon/female/419.png and b/public/images/pokemon/female/419.png differ diff --git a/public/images/pokemon/female/424.png b/public/images/pokemon/female/424.png index 8e4fbc235ca..4d86b5be16e 100644 Binary files a/public/images/pokemon/female/424.png and b/public/images/pokemon/female/424.png differ diff --git a/public/images/pokemon/female/45.png b/public/images/pokemon/female/45.png index fad183bbf8d..3c5d4d91aeb 100644 Binary files a/public/images/pokemon/female/45.png and b/public/images/pokemon/female/45.png differ diff --git a/public/images/pokemon/female/456.png b/public/images/pokemon/female/456.png index a50fd2aa034..ca0d454c98f 100644 Binary files a/public/images/pokemon/female/456.png and b/public/images/pokemon/female/456.png differ diff --git a/public/images/pokemon/female/457.png b/public/images/pokemon/female/457.png index 2aec3e26a07..605bb23a892 100644 Binary files a/public/images/pokemon/female/457.png and b/public/images/pokemon/female/457.png differ diff --git a/public/images/pokemon/icons/2/154-f.png b/public/images/pokemon/icons/2/154-f.png new file mode 100644 index 00000000000..6481cdd8a00 Binary files /dev/null and b/public/images/pokemon/icons/2/154-f.png differ diff --git a/public/images/pokemon/icons/2/154s-f.png b/public/images/pokemon/icons/2/154s-f.png new file mode 100644 index 00000000000..44ded711dcd Binary files /dev/null and b/public/images/pokemon/icons/2/154s-f.png differ diff --git a/public/images/pokemon/icons/3/255-f.png b/public/images/pokemon/icons/3/255-f.png new file mode 100644 index 00000000000..bb221be21e7 Binary files /dev/null and b/public/images/pokemon/icons/3/255-f.png differ diff --git a/public/images/pokemon/icons/3/255s-f.png b/public/images/pokemon/icons/3/255s-f.png new file mode 100644 index 00000000000..898b17c163c Binary files /dev/null and b/public/images/pokemon/icons/3/255s-f.png differ diff --git a/public/images/pokemon/icons/3/256-f.png b/public/images/pokemon/icons/3/256-f.png new file mode 100644 index 00000000000..72800cc5e25 Binary files /dev/null and b/public/images/pokemon/icons/3/256-f.png differ diff --git a/public/images/pokemon/icons/3/256s-f.png b/public/images/pokemon/icons/3/256s-f.png new file mode 100644 index 00000000000..ce6608f7bc5 Binary files /dev/null and b/public/images/pokemon/icons/3/256s-f.png differ diff --git a/public/images/pokemon/icons/3/257-f-mega.png b/public/images/pokemon/icons/3/257-f-mega.png new file mode 100644 index 00000000000..ed64fe8f41f Binary files /dev/null and b/public/images/pokemon/icons/3/257-f-mega.png differ diff --git a/public/images/pokemon/icons/3/257-f.png b/public/images/pokemon/icons/3/257-f.png new file mode 100644 index 00000000000..ee42a9f75c0 Binary files /dev/null and b/public/images/pokemon/icons/3/257-f.png differ diff --git a/public/images/pokemon/icons/3/257s-f-mega.png b/public/images/pokemon/icons/3/257s-f-mega.png new file mode 100644 index 00000000000..faf5e5aa30c Binary files /dev/null and b/public/images/pokemon/icons/3/257s-f-mega.png differ diff --git a/public/images/pokemon/icons/3/257s-f.png b/public/images/pokemon/icons/3/257s-f.png new file mode 100644 index 00000000000..bf59393972f Binary files /dev/null and b/public/images/pokemon/icons/3/257s-f.png differ diff --git a/public/images/pokemon/shiny/1003.png b/public/images/pokemon/shiny/1003.png index 3c5415cf94e..17462eaf89f 100644 Binary files a/public/images/pokemon/shiny/1003.png and b/public/images/pokemon/shiny/1003.png differ diff --git a/public/images/pokemon/shiny/1018.png b/public/images/pokemon/shiny/1018.png index 7c753bb05e5..42076fd0687 100644 Binary files a/public/images/pokemon/shiny/1018.png and b/public/images/pokemon/shiny/1018.png differ diff --git a/public/images/pokemon/shiny/164.png b/public/images/pokemon/shiny/164.png index de6405ff580..4e36ce61adb 100644 Binary files a/public/images/pokemon/shiny/164.png and b/public/images/pokemon/shiny/164.png differ diff --git a/public/images/pokemon/shiny/190.png b/public/images/pokemon/shiny/190.png index 3ad2e7b5582..60b7ad3163d 100644 Binary files a/public/images/pokemon/shiny/190.png and b/public/images/pokemon/shiny/190.png differ diff --git a/public/images/pokemon/shiny/218.png b/public/images/pokemon/shiny/218.png index cd70183de39..b3e42ae4adc 100644 Binary files a/public/images/pokemon/shiny/218.png and b/public/images/pokemon/shiny/218.png differ diff --git a/public/images/pokemon/shiny/226.png b/public/images/pokemon/shiny/226.png index 5b24827b5d5..f2934a577f1 100644 Binary files a/public/images/pokemon/shiny/226.png and b/public/images/pokemon/shiny/226.png differ diff --git a/public/images/pokemon/shiny/261.png b/public/images/pokemon/shiny/261.png index 1a976339b67..6ae6e5838ce 100644 Binary files a/public/images/pokemon/shiny/261.png and b/public/images/pokemon/shiny/261.png differ diff --git a/public/images/pokemon/shiny/262.png b/public/images/pokemon/shiny/262.png index ea42b525b6b..d2dbec0bb8d 100644 Binary files a/public/images/pokemon/shiny/262.png and b/public/images/pokemon/shiny/262.png differ diff --git a/public/images/pokemon/shiny/308.png b/public/images/pokemon/shiny/308.png index 14e97cf83a3..102bc0256e4 100644 Binary files a/public/images/pokemon/shiny/308.png and b/public/images/pokemon/shiny/308.png differ diff --git a/public/images/pokemon/shiny/335.png b/public/images/pokemon/shiny/335.png index e4f8d10fb1b..765344af6fd 100644 Binary files a/public/images/pokemon/shiny/335.png and b/public/images/pokemon/shiny/335.png differ diff --git a/public/images/pokemon/shiny/336.png b/public/images/pokemon/shiny/336.png index efa1262248a..45d57027704 100644 Binary files a/public/images/pokemon/shiny/336.png and b/public/images/pokemon/shiny/336.png differ diff --git a/public/images/pokemon/shiny/357.png b/public/images/pokemon/shiny/357.png index ca74e4b466c..0eb62e53719 100644 Binary files a/public/images/pokemon/shiny/357.png and b/public/images/pokemon/shiny/357.png differ diff --git a/public/images/pokemon/shiny/370.png b/public/images/pokemon/shiny/370.png index 8a7bf0b11a8..f49fe4fe27c 100644 Binary files a/public/images/pokemon/shiny/370.png and b/public/images/pokemon/shiny/370.png differ diff --git a/public/images/pokemon/shiny/373-mega.png b/public/images/pokemon/shiny/373-mega.png index 4e9fe93fa5c..fd2e8eed6e2 100644 Binary files a/public/images/pokemon/shiny/373-mega.png and b/public/images/pokemon/shiny/373-mega.png differ diff --git a/public/images/pokemon/shiny/373.png b/public/images/pokemon/shiny/373.png index cae369ce412..6e21c49c034 100644 Binary files a/public/images/pokemon/shiny/373.png and b/public/images/pokemon/shiny/373.png differ diff --git a/public/images/pokemon/shiny/4080.png b/public/images/pokemon/shiny/4080.png index 9ff6184d692..35f3c7c0ff6 100644 Binary files a/public/images/pokemon/shiny/4080.png and b/public/images/pokemon/shiny/4080.png differ diff --git a/public/images/pokemon/shiny/4199.png b/public/images/pokemon/shiny/4199.png index d428931eff8..fb4b8b70ddb 100644 Binary files a/public/images/pokemon/shiny/4199.png and b/public/images/pokemon/shiny/4199.png differ diff --git a/public/images/pokemon/shiny/424.png b/public/images/pokemon/shiny/424.png index 0c97de2883e..5685c21e276 100644 Binary files a/public/images/pokemon/shiny/424.png and b/public/images/pokemon/shiny/424.png differ diff --git a/public/images/pokemon/shiny/433.png b/public/images/pokemon/shiny/433.png index 55021a56458..f5f34928c21 100644 Binary files a/public/images/pokemon/shiny/433.png and b/public/images/pokemon/shiny/433.png differ diff --git a/public/images/pokemon/shiny/45.png b/public/images/pokemon/shiny/45.png index 92b28c054c9..4aab94dfb65 100644 Binary files a/public/images/pokemon/shiny/45.png and b/public/images/pokemon/shiny/45.png differ diff --git a/public/images/pokemon/shiny/451.json b/public/images/pokemon/shiny/451.json index f492cdbcb04..273d546e2bb 100644 --- a/public/images/pokemon/shiny/451.json +++ b/public/images/pokemon/shiny/451.json @@ -1,2330 +1,715 @@ -{ - "textures": [ - { - "image": "451.png", - "format": "RGBA8888", - "size": { - "w": 281, - "h": 281 - }, - "scale": 1, - "frames": [ - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 5, - "w": 69, - "h": 41 - }, - "frame": { - "x": 0, - "y": 0, - "w": 69, - "h": 41 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 63, - "h": 43 - }, - "frame": { - "x": 69, - "y": 0, - "w": 63, - "h": 43 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 66, - "h": 43 - }, - "frame": { - "x": 132, - "y": 0, - "w": 66, - "h": 43 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 198, - "y": 0, - "w": 65, - "h": 43 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 3, - "w": 65, - "h": 43 - }, - "frame": { - "x": 0, - "y": 41, - "w": 65, - "h": 43 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 61, - "h": 44 - }, - "frame": { - "x": 65, - "y": 43, - "w": 61, - "h": 44 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 4, - "y": 2, - "w": 62, - "h": 44 - }, - "frame": { - "x": 126, - "y": 43, - "w": 62, - "h": 44 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 2, - "w": 63, - "h": 44 - }, - "frame": { - "x": 188, - "y": 43, - "w": 63, - "h": 44 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 0, - "y": 84, - "w": 59, - "h": 45 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 1, - "w": 59, - "h": 45 - }, - "frame": { - "x": 59, - "y": 87, - "w": 59, - "h": 45 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 61, - "h": 45 - }, - "frame": { - "x": 118, - "y": 87, - "w": 61, - "h": 45 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 179, - "y": 87, - "w": 57, - "h": 45 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 179, - "y": 87, - "w": 57, - "h": 45 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 0, - "y": 129, - "w": 57, - "h": 45 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 0, - "y": 129, - "w": 57, - "h": 45 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 57, - "y": 132, - "w": 57, - "h": 45 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 57, - "h": 45 - }, - "frame": { - "x": 57, - "y": 132, - "w": 57, - "h": 45 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 56, - "h": 45 - }, - "frame": { - "x": 114, - "y": 132, - "w": 56, - "h": 45 - } - }, - { - "filename": "0096.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 56, - "h": 45 - }, - "frame": { - "x": 114, - "y": 132, - "w": 56, - "h": 45 - } - }, - { - "filename": "0097.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 170, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0098.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 170, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0099.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 0, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 224, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0100.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 0, - "y": 1, - "w": 54, - "h": 45 - }, - "frame": { - "x": 224, - "y": 132, - "w": 54, - "h": 45 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 174, - "w": 54, - "h": 46 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 111, - "y": 177, - "w": 57, - "h": 46 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 60, - "h": 46 - }, - "frame": { - "x": 168, - "y": 177, - "w": 60, - "h": 46 - } - }, - { - "filename": "0109.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 53, - "h": 46 - }, - "frame": { - "x": 228, - "y": 177, - "w": 53, - "h": 46 - } - }, - { - "filename": "0110.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 53, - "h": 46 - }, - "frame": { - "x": 228, - "y": 177, - "w": 53, - "h": 46 - } - }, - { - "filename": "0101.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 220, - "w": 54, - "h": 46 - } - }, - { - "filename": "0102.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 0, - "y": 220, - "w": 54, - "h": 46 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 57, - "h": 46 - }, - "frame": { - "x": 54, - "y": 223, - "w": 57, - "h": 46 - } - }, - { - "filename": "0103.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 111, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0104.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 111, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0105.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 166, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0106.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 55, - "h": 46 - }, - "frame": { - "x": 166, - "y": 223, - "w": 55, - "h": 46 - } - }, - { - "filename": "0107.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 221, - "y": 223, - "w": 54, - "h": 46 - } - }, - { - "filename": "0108.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 71, - "h": 46 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 54, - "h": 46 - }, - "frame": { - "x": 221, - "y": 223, - "w": 54, - "h": 46 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:9097bf5ffed4b93401c65aa14299faaa:d22b7c7f6e33b1453fda428e689d4529:c79e17c206de27e3b7f1ce96f7df8e51$" - } +{ "frames": [ + { + "filename": "0055.png", + "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0001.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0002.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0003.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0004.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0005.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0007.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0008.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0009.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0010.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0011.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0012.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0013.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0014.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0015.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0016.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0017.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0018.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0019.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0020.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0021.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0022.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0023.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0024.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0025.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0026.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0027.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0028.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0029.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0030.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0031.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0032.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0033.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0034.png", + "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0035.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0036.png", + "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0037.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0038.png", + "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0039.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0040.png", + "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0041.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0042.png", + "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0043.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0044.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0045.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0046.png", + "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0047.png", + "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0048.png", + "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0049.png", + "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0050.png", + "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0051.png", + "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0052.png", + "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0053.png", + "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0054.png", + "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0056.png", + "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0057.png", + "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0058.png", + "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0059.png", + "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0060.png", + "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0061.png", + "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0062.png", + "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0063.png", + "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0064.png", + "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0065.png", + "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0066.png", + "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0067.png", + "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0068.png", + "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0069.png", + "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0070.png", + "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0071.png", + "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0072.png", + "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0073.png", + "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0074.png", + "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0075.png", + "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0076.png", + "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0077.png", + "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0078.png", + "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0079.png", + "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0080.png", + "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0081.png", + "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0082.png", + "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0083.png", + "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0084.png", + "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0085.png", + "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0086.png", + "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0087.png", + "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + }, + { + "filename": "0088.png", + "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, + "sourceSize": { "w": 71, "h": 46 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "451.png", + "format": "I8", + "size": { "w": 339, "h": 221 }, + "scale": "1" + } } diff --git a/public/images/pokemon/shiny/451.png b/public/images/pokemon/shiny/451.png index 69d165c9ae8..4f8120ce668 100644 Binary files a/public/images/pokemon/shiny/451.png and b/public/images/pokemon/shiny/451.png differ diff --git a/public/images/pokemon/shiny/469.png b/public/images/pokemon/shiny/469.png index c7f7ee3b68e..7e8d8b674d3 100644 Binary files a/public/images/pokemon/shiny/469.png and b/public/images/pokemon/shiny/469.png differ diff --git a/public/images/pokemon/shiny/47.json b/public/images/pokemon/shiny/47.json index 185bcf4b527..0aa03eab585 100644 --- a/public/images/pokemon/shiny/47.json +++ b/public/images/pokemon/shiny/47.json @@ -1,776 +1,299 @@ -{ - "textures": [ - { - "image": "47.png", - "format": "RGBA8888", - "size": { - "w": 230, - "h": 230 - }, - "scale": 1, - "frames": [ - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 12, - "w": 56, - "h": 49 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 49 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 12, - "w": 56, - "h": 49 - }, - "frame": { - "x": 0, - "y": 0, - "w": 56, - "h": 49 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 12, - "w": 62, - "h": 51 - }, - "frame": { - "x": 56, - "y": 0, - "w": 62, - "h": 51 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 12, - "w": 62, - "h": 51 - }, - "frame": { - "x": 56, - "y": 0, - "w": 62, - "h": 51 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 8, - "w": 55, - "h": 53 - }, - "frame": { - "x": 118, - "y": 0, - "w": 55, - "h": 53 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 8, - "w": 55, - "h": 53 - }, - "frame": { - "x": 118, - "y": 0, - "w": 55, - "h": 53 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 55, - "h": 54 - }, - "frame": { - "x": 173, - "y": 0, - "w": 55, - "h": 54 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 55, - "h": 54 - }, - "frame": { - "x": 173, - "y": 0, - "w": 55, - "h": 54 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 53, - "h": 56 - }, - "frame": { - "x": 0, - "y": 49, - "w": 53, - "h": 56 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 53, - "h": 56 - }, - "frame": { - "x": 0, - "y": 49, - "w": 53, - "h": 56 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 62, - "h": 55 - }, - "frame": { - "x": 53, - "y": 51, - "w": 62, - "h": 55 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 8, - "w": 62, - "h": 55 - }, - "frame": { - "x": 53, - "y": 51, - "w": 62, - "h": 55 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 53, - "h": 57 - }, - "frame": { - "x": 115, - "y": 53, - "w": 53, - "h": 57 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 8, - "y": 4, - "w": 53, - "h": 57 - }, - "frame": { - "x": 115, - "y": 53, - "w": 53, - "h": 57 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 62, - "h": 56 - }, - "frame": { - "x": 168, - "y": 54, - "w": 62, - "h": 56 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 7, - "w": 62, - "h": 56 - }, - "frame": { - "x": 168, - "y": 54, - "w": 62, - "h": 56 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 61, - "h": 58 - }, - "frame": { - "x": 0, - "y": 106, - "w": 61, - "h": 58 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 5, - "w": 61, - "h": 58 - }, - "frame": { - "x": 0, - "y": 106, - "w": 61, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 61, - "y": 106, - "w": 54, - "h": 59 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 61, - "y": 106, - "w": 54, - "h": 59 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 2, - "w": 57, - "h": 59 - }, - "frame": { - "x": 115, - "y": 110, - "w": 57, - "h": 59 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 172, - "y": 110, - "w": 54, - "h": 59 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 6, - "y": 2, - "w": 54, - "h": 59 - }, - "frame": { - "x": 172, - "y": 110, - "w": 54, - "h": 59 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 61, - "h": 59 - }, - "frame": { - "x": 0, - "y": 164, - "w": 61, - "h": 59 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 61, - "h": 59 - }, - "frame": { - "x": 0, - "y": 164, - "w": 61, - "h": 59 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 57, - "h": 61 - }, - "frame": { - "x": 61, - "y": 169, - "w": 57, - "h": 61 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 62, - "h": 63 - }, - "spriteSourceSize": { - "x": 0, - "y": 2, - "w": 60, - "h": 61 - }, - "frame": { - "x": 118, - "y": 169, - "w": 60, - "h": 61 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:38ba9918eb8f68ab2190b03c6512ef47:46578d6dd482a1b04fa7c2884107a0f5:fe45e2d628a6cef0908f7b82468c8798$" - } -} \ No newline at end of file +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0002.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0003.png", + "frame": { "x": 166, "y": 114, "w": 52, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0004.png", + "frame": { "x": 166, "y": 114, "w": 52, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0005.png", + "frame": { "x": 0, "y": 169, "w": 51, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 7, "w": 51, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0006.png", + "frame": { "x": 0, "y": 169, "w": 51, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 7, "w": 51, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0007.png", + "frame": { "x": 104, "y": 166, "w": 53, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 9, "w": 53, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0008.png", + "frame": { "x": 104, "y": 166, "w": 53, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 9, "w": 53, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0009.png", + "frame": { "x": 157, "y": 170, "w": 55, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 12, "w": 55, "h": 49 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0010.png", + "frame": { "x": 157, "y": 170, "w": 55, "h": 49 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 12, "w": 55, "h": 49 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0011.png", + "frame": { "x": 0, "y": 115, "w": 53, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 8, "w": 53, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0012.png", + "frame": { "x": 0, "y": 115, "w": 53, "h": 54 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 8, "w": 53, "h": 54 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0013.png", + "frame": { "x": 53, "y": 116, "w": 51, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 6, "w": 51, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0014.png", + "frame": { "x": 53, "y": 116, "w": 51, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 11, "y": 6, "w": 51, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0015.png", + "frame": { "x": 114, "y": 109, "w": 52, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0016.png", + "frame": { "x": 114, "y": 109, "w": 52, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 9, "y": 5, "w": 52, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0017.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0018.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0019.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0020.png", + "frame": { "x": 0, "y": 58, "w": 55, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 5, "w": 55, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0021.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0022.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0023.png", + "frame": { "x": 119, "y": 0, "w": 59, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 7, "w": 59, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0024.png", + "frame": { "x": 119, "y": 0, "w": 59, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 7, "w": 59, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0025.png", + "frame": { "x": 178, "y": 0, "w": 60, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 9, "w": 60, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0026.png", + "frame": { "x": 178, "y": 0, "w": 60, "h": 56 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 9, "w": 60, "h": 56 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0027.png", + "frame": { "x": 114, "y": 57, "w": 62, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 12, "w": 62, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0028.png", + "frame": { "x": 114, "y": 57, "w": 62, "h": 52 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 12, "w": 62, "h": 52 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0029.png", + "frame": { "x": 59, "y": 0, "w": 60, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 8, "w": 60, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0030.png", + "frame": { "x": 59, "y": 0, "w": 60, "h": 57 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 8, "w": 60, "h": 57 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0031.png", + "frame": { "x": 0, "y": 0, "w": 59, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 59, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0032.png", + "frame": { "x": 0, "y": 0, "w": 59, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 6, "w": 59, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0033.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0034.png", + "frame": { "x": 178, "y": 56, "w": 57, "h": 58 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 4, "y": 5, "w": 57, "h": 58 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0035.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + }, + { + "filename": "0036.png", + "frame": { "x": 59, "y": 57, "w": 55, "h": 59 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 6, "y": 3, "w": 55, "h": 59 }, + "sourceSize": { "w": 65, "h": 65 } + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.8.1-x64", + "image": "47.png", + "format": "I8", + "size": { "w": 238, "h": 223 }, + "scale": "1" + } +} diff --git a/public/images/pokemon/shiny/47.png b/public/images/pokemon/shiny/47.png index ba5f60d1b8f..b1b0bb9a12a 100644 Binary files a/public/images/pokemon/shiny/47.png and b/public/images/pokemon/shiny/47.png differ diff --git a/public/images/pokemon/shiny/472.png b/public/images/pokemon/shiny/472.png index f1e833b66cb..14fa9b0fe8b 100644 Binary files a/public/images/pokemon/shiny/472.png and b/public/images/pokemon/shiny/472.png differ diff --git a/public/images/pokemon/shiny/477.png b/public/images/pokemon/shiny/477.png index 0f14172f019..2011636c517 100644 Binary files a/public/images/pokemon/shiny/477.png and b/public/images/pokemon/shiny/477.png differ diff --git a/public/images/pokemon/shiny/556.png b/public/images/pokemon/shiny/556.png index b0052759531..d62d114bbcb 100644 Binary files a/public/images/pokemon/shiny/556.png and b/public/images/pokemon/shiny/556.png differ diff --git a/public/images/pokemon/shiny/677.png b/public/images/pokemon/shiny/677.png index b90d0286879..a885bec4ee0 100644 Binary files a/public/images/pokemon/shiny/677.png and b/public/images/pokemon/shiny/677.png differ diff --git a/public/images/pokemon/shiny/678-female.png b/public/images/pokemon/shiny/678-female.png index 9a288a4fcb4..e999a0fbd69 100644 Binary files a/public/images/pokemon/shiny/678-female.png and b/public/images/pokemon/shiny/678-female.png differ diff --git a/public/images/pokemon/shiny/678.png b/public/images/pokemon/shiny/678.png index da33c6b3645..683d92c3299 100644 Binary files a/public/images/pokemon/shiny/678.png and b/public/images/pokemon/shiny/678.png differ diff --git a/public/images/pokemon/shiny/698.png b/public/images/pokemon/shiny/698.png index 0874f9af37e..c04cc11875d 100644 Binary files a/public/images/pokemon/shiny/698.png and b/public/images/pokemon/shiny/698.png differ diff --git a/public/images/pokemon/shiny/699.png b/public/images/pokemon/shiny/699.png index 16876941901..1e22a84228f 100644 Binary files a/public/images/pokemon/shiny/699.png and b/public/images/pokemon/shiny/699.png differ diff --git a/public/images/pokemon/shiny/716-active.png b/public/images/pokemon/shiny/716-active.png index 077c657f814..357f0fb843b 100644 Binary files a/public/images/pokemon/shiny/716-active.png and b/public/images/pokemon/shiny/716-active.png differ diff --git a/public/images/pokemon/shiny/716-neutral.png b/public/images/pokemon/shiny/716-neutral.png index ffe04571aa7..bcccc513da4 100644 Binary files a/public/images/pokemon/shiny/716-neutral.png and b/public/images/pokemon/shiny/716-neutral.png differ diff --git a/public/images/pokemon/shiny/772.png b/public/images/pokemon/shiny/772.png index f2038ddd4fa..759691cc312 100644 Binary files a/public/images/pokemon/shiny/772.png and b/public/images/pokemon/shiny/772.png differ diff --git a/public/images/pokemon/shiny/773.png b/public/images/pokemon/shiny/773.png index 3b2b7f9b296..6f900ae9fdc 100644 Binary files a/public/images/pokemon/shiny/773.png and b/public/images/pokemon/shiny/773.png differ diff --git a/public/images/pokemon/shiny/777.png b/public/images/pokemon/shiny/777.png index c8ba24bf3ac..8ceb5506f9b 100644 Binary files a/public/images/pokemon/shiny/777.png and b/public/images/pokemon/shiny/777.png differ diff --git a/public/images/pokemon/shiny/80-mega.png b/public/images/pokemon/shiny/80-mega.png index 399402cf618..21eb3c37a05 100644 Binary files a/public/images/pokemon/shiny/80-mega.png and b/public/images/pokemon/shiny/80-mega.png differ diff --git a/public/images/pokemon/shiny/80.png b/public/images/pokemon/shiny/80.png index 750f0d7cc92..a40953ebc30 100644 Binary files a/public/images/pokemon/shiny/80.png and b/public/images/pokemon/shiny/80.png differ diff --git a/public/images/pokemon/shiny/818-gigantamax.png b/public/images/pokemon/shiny/818-gigantamax.png index 694692964fb..6ff10199f70 100644 Binary files a/public/images/pokemon/shiny/818-gigantamax.png and b/public/images/pokemon/shiny/818-gigantamax.png differ diff --git a/public/images/pokemon/shiny/862.png b/public/images/pokemon/shiny/862.png index bc949797ee9..e48ad78032b 100644 Binary files a/public/images/pokemon/shiny/862.png and b/public/images/pokemon/shiny/862.png differ diff --git a/public/images/pokemon/shiny/864.png b/public/images/pokemon/shiny/864.png index 780f52945f2..383dfecc748 100644 Binary files a/public/images/pokemon/shiny/864.png and b/public/images/pokemon/shiny/864.png differ diff --git a/public/images/pokemon/shiny/898-ice.png b/public/images/pokemon/shiny/898-ice.png index d262f8c3e9d..ae98d45e159 100644 Binary files a/public/images/pokemon/shiny/898-ice.png and b/public/images/pokemon/shiny/898-ice.png differ diff --git a/public/images/pokemon/shiny/898-shadow.png b/public/images/pokemon/shiny/898-shadow.png index 6744535f165..6d056c192b1 100644 Binary files a/public/images/pokemon/shiny/898-shadow.png and b/public/images/pokemon/shiny/898-shadow.png differ diff --git a/public/images/pokemon/shiny/912.png b/public/images/pokemon/shiny/912.png index 9fcdbbae78f..70067668d6a 100644 Binary files a/public/images/pokemon/shiny/912.png and b/public/images/pokemon/shiny/912.png differ diff --git a/public/images/pokemon/shiny/913.png b/public/images/pokemon/shiny/913.png index 5791f5be095..08eb57e1957 100644 Binary files a/public/images/pokemon/shiny/913.png and b/public/images/pokemon/shiny/913.png differ diff --git a/public/images/pokemon/shiny/914.png b/public/images/pokemon/shiny/914.png index f20466d543d..950f964aa09 100644 Binary files a/public/images/pokemon/shiny/914.png and b/public/images/pokemon/shiny/914.png differ diff --git a/public/images/pokemon/shiny/940.png b/public/images/pokemon/shiny/940.png index 77b45558e2c..a953bd6fc44 100644 Binary files a/public/images/pokemon/shiny/940.png and b/public/images/pokemon/shiny/940.png differ diff --git a/public/images/pokemon/shiny/941.png b/public/images/pokemon/shiny/941.png index 986a6a53a8e..5ccba807754 100644 Binary files a/public/images/pokemon/shiny/941.png and b/public/images/pokemon/shiny/941.png differ diff --git a/public/images/pokemon/shiny/981.png b/public/images/pokemon/shiny/981.png index 68570ed91f2..4b1ea705a5c 100644 Binary files a/public/images/pokemon/shiny/981.png and b/public/images/pokemon/shiny/981.png differ diff --git a/public/images/pokemon/shiny/997.png b/public/images/pokemon/shiny/997.png index a9efb86b999..e164116e56c 100644 Binary files a/public/images/pokemon/shiny/997.png and b/public/images/pokemon/shiny/997.png differ diff --git a/public/images/pokemon/shiny/female/190.png b/public/images/pokemon/shiny/female/190.png index 0fc6b5f08dd..c79a1f85fe2 100644 Binary files a/public/images/pokemon/shiny/female/190.png and b/public/images/pokemon/shiny/female/190.png differ diff --git a/public/images/pokemon/shiny/female/424.png b/public/images/pokemon/shiny/female/424.png index 84ed5063655..f84f91548c9 100644 Binary files a/public/images/pokemon/shiny/female/424.png and b/public/images/pokemon/shiny/female/424.png differ diff --git a/public/images/pokemon/shiny/female/45.png b/public/images/pokemon/shiny/female/45.png index a6bbe3039cb..2ea6e0d214b 100644 Binary files a/public/images/pokemon/shiny/female/45.png and b/public/images/pokemon/shiny/female/45.png differ diff --git a/public/images/pokemon/variant/113_1.png b/public/images/pokemon/variant/113_1.png index bf4ce11b3d7..b64aca2eb7e 100644 Binary files a/public/images/pokemon/variant/113_1.png and b/public/images/pokemon/variant/113_1.png differ diff --git a/public/images/pokemon/variant/113_2.png b/public/images/pokemon/variant/113_2.png index 455068d0c66..d89db9f3b73 100644 Binary files a/public/images/pokemon/variant/113_2.png and b/public/images/pokemon/variant/113_2.png differ diff --git a/public/images/pokemon/variant/113_3.png b/public/images/pokemon/variant/113_3.png index 3a82615fcca..2623a4c5527 100644 Binary files a/public/images/pokemon/variant/113_3.png and b/public/images/pokemon/variant/113_3.png differ diff --git a/public/images/pokemon/variant/125_3.png b/public/images/pokemon/variant/125_3.png index 89c02510442..dfab3fd2b7a 100644 Binary files a/public/images/pokemon/variant/125_3.png and b/public/images/pokemon/variant/125_3.png differ diff --git a/public/images/pokemon/variant/126_2.png b/public/images/pokemon/variant/126_2.png index 19aa928037d..12ddf148e51 100644 Binary files a/public/images/pokemon/variant/126_2.png and b/public/images/pokemon/variant/126_2.png differ diff --git a/public/images/pokemon/variant/139_3.png b/public/images/pokemon/variant/139_3.png index 0c6bd552707..90f8728ffc7 100644 Binary files a/public/images/pokemon/variant/139_3.png and b/public/images/pokemon/variant/139_3.png differ diff --git a/public/images/pokemon/variant/141_2.png b/public/images/pokemon/variant/141_2.png index 3cd10984009..c94613ee202 100644 Binary files a/public/images/pokemon/variant/141_2.png and b/public/images/pokemon/variant/141_2.png differ diff --git a/public/images/pokemon/variant/141_3.png b/public/images/pokemon/variant/141_3.png index 271e5310b84..74ff87613d6 100644 Binary files a/public/images/pokemon/variant/141_3.png and b/public/images/pokemon/variant/141_3.png differ diff --git a/public/images/pokemon/variant/144_2.png b/public/images/pokemon/variant/144_2.png index 9c5ff41d5c5..ca20163748d 100644 Binary files a/public/images/pokemon/variant/144_2.png and b/public/images/pokemon/variant/144_2.png differ diff --git a/public/images/pokemon/variant/144_3.png b/public/images/pokemon/variant/144_3.png index 52e8bb092f1..a14e9166803 100644 Binary files a/public/images/pokemon/variant/144_3.png and b/public/images/pokemon/variant/144_3.png differ diff --git a/public/images/pokemon/variant/161_3.png b/public/images/pokemon/variant/161_3.png index a26adbff423..f4badb44d7f 100644 Binary files a/public/images/pokemon/variant/161_3.png and b/public/images/pokemon/variant/161_3.png differ diff --git a/public/images/pokemon/variant/164_2.png b/public/images/pokemon/variant/164_2.png index 0bd9234ca76..a5809a334f9 100644 Binary files a/public/images/pokemon/variant/164_2.png and b/public/images/pokemon/variant/164_2.png differ diff --git a/public/images/pokemon/variant/164_3.png b/public/images/pokemon/variant/164_3.png index fb3c689aa05..38c88b30a49 100644 Binary files a/public/images/pokemon/variant/164_3.png and b/public/images/pokemon/variant/164_3.png differ diff --git a/public/images/pokemon/variant/173_3.png b/public/images/pokemon/variant/173_3.png index 5cb3abbaaec..2799a88a0df 100644 Binary files a/public/images/pokemon/variant/173_3.png and b/public/images/pokemon/variant/173_3.png differ diff --git a/public/images/pokemon/variant/180_2.png b/public/images/pokemon/variant/180_2.png index 522014a78ba..fbdd97e2d52 100644 Binary files a/public/images/pokemon/variant/180_2.png and b/public/images/pokemon/variant/180_2.png differ diff --git a/public/images/pokemon/variant/180_3.png b/public/images/pokemon/variant/180_3.png index 074a4d06b80..382b3ea0961 100644 Binary files a/public/images/pokemon/variant/180_3.png and b/public/images/pokemon/variant/180_3.png differ diff --git a/public/images/pokemon/variant/181-mega_3.png b/public/images/pokemon/variant/181-mega_3.png index 444947d3207..4adad450342 100644 Binary files a/public/images/pokemon/variant/181-mega_3.png and b/public/images/pokemon/variant/181-mega_3.png differ diff --git a/public/images/pokemon/variant/183_3.png b/public/images/pokemon/variant/183_3.png index ba04c8da2d7..479164bc4f8 100644 Binary files a/public/images/pokemon/variant/183_3.png and b/public/images/pokemon/variant/183_3.png differ diff --git a/public/images/pokemon/variant/184_2.png b/public/images/pokemon/variant/184_2.png index dc166ced0db..fd57af02f36 100644 Binary files a/public/images/pokemon/variant/184_2.png and b/public/images/pokemon/variant/184_2.png differ diff --git a/public/images/pokemon/variant/184_3.png b/public/images/pokemon/variant/184_3.png index edde99b92ff..09fa5274381 100644 Binary files a/public/images/pokemon/variant/184_3.png and b/public/images/pokemon/variant/184_3.png differ diff --git a/public/images/pokemon/variant/199_1.png b/public/images/pokemon/variant/199_1.png index 9899cae4e7f..3366b156560 100644 Binary files a/public/images/pokemon/variant/199_1.png and b/public/images/pokemon/variant/199_1.png differ diff --git a/public/images/pokemon/variant/212-mega_2.png b/public/images/pokemon/variant/212-mega_2.png index a4db96d6aac..e3d12893a29 100644 Binary files a/public/images/pokemon/variant/212-mega_2.png and b/public/images/pokemon/variant/212-mega_2.png differ diff --git a/public/images/pokemon/variant/212-mega_3.png b/public/images/pokemon/variant/212-mega_3.png index dac4b3bd552..9d7ba9e777e 100644 Binary files a/public/images/pokemon/variant/212-mega_3.png and b/public/images/pokemon/variant/212-mega_3.png differ diff --git a/public/images/pokemon/variant/212_2.png b/public/images/pokemon/variant/212_2.png index 913ab71026d..a22ed57eaf3 100644 Binary files a/public/images/pokemon/variant/212_2.png and b/public/images/pokemon/variant/212_2.png differ diff --git a/public/images/pokemon/variant/212_3.png b/public/images/pokemon/variant/212_3.png index 6a027cd53dc..1a71f292f28 100644 Binary files a/public/images/pokemon/variant/212_3.png and b/public/images/pokemon/variant/212_3.png differ diff --git a/public/images/pokemon/variant/226_2.png b/public/images/pokemon/variant/226_2.png index 770f77e7154..2d011894969 100644 Binary files a/public/images/pokemon/variant/226_2.png and b/public/images/pokemon/variant/226_2.png differ diff --git a/public/images/pokemon/variant/226_3.png b/public/images/pokemon/variant/226_3.png index eff63a19224..d0ad24bc78d 100644 Binary files a/public/images/pokemon/variant/226_3.png and b/public/images/pokemon/variant/226_3.png differ diff --git a/public/images/pokemon/variant/239_3.png b/public/images/pokemon/variant/239_3.png index 25f32c3f96c..158d8701098 100644 Binary files a/public/images/pokemon/variant/239_3.png and b/public/images/pokemon/variant/239_3.png differ diff --git a/public/images/pokemon/variant/242_1.png b/public/images/pokemon/variant/242_1.png index 987c4d5a9f9..8b5cff906e4 100644 Binary files a/public/images/pokemon/variant/242_1.png and b/public/images/pokemon/variant/242_1.png differ diff --git a/public/images/pokemon/variant/242_2.png b/public/images/pokemon/variant/242_2.png index 276d36f7091..aefddbd0cf8 100644 Binary files a/public/images/pokemon/variant/242_2.png and b/public/images/pokemon/variant/242_2.png differ diff --git a/public/images/pokemon/variant/242_3.png b/public/images/pokemon/variant/242_3.png index 6f83ed75c95..bc121988137 100644 Binary files a/public/images/pokemon/variant/242_3.png and b/public/images/pokemon/variant/242_3.png differ diff --git a/public/images/pokemon/variant/244_2.png b/public/images/pokemon/variant/244_2.png index 2b7b4e57b97..b6418e496f6 100644 Binary files a/public/images/pokemon/variant/244_2.png and b/public/images/pokemon/variant/244_2.png differ diff --git a/public/images/pokemon/variant/244_3.png b/public/images/pokemon/variant/244_3.png index 93b51f45f2f..c903b01d8ce 100644 Binary files a/public/images/pokemon/variant/244_3.png and b/public/images/pokemon/variant/244_3.png differ diff --git a/public/images/pokemon/variant/248-mega_2.png b/public/images/pokemon/variant/248-mega_2.png index a6000c370a7..1aade727abf 100644 Binary files a/public/images/pokemon/variant/248-mega_2.png and b/public/images/pokemon/variant/248-mega_2.png differ diff --git a/public/images/pokemon/variant/249_2.png b/public/images/pokemon/variant/249_2.png index 220258fb7f8..d896fa2999f 100644 Binary files a/public/images/pokemon/variant/249_2.png and b/public/images/pokemon/variant/249_2.png differ diff --git a/public/images/pokemon/variant/249_3.png b/public/images/pokemon/variant/249_3.png index 85a453e0ad5..7a932d917fb 100644 Binary files a/public/images/pokemon/variant/249_3.png and b/public/images/pokemon/variant/249_3.png differ diff --git a/public/images/pokemon/variant/250_2.png b/public/images/pokemon/variant/250_2.png index 5c1d4828ebe..d63c5310db8 100644 Binary files a/public/images/pokemon/variant/250_2.png and b/public/images/pokemon/variant/250_2.png differ diff --git a/public/images/pokemon/variant/250_3.png b/public/images/pokemon/variant/250_3.png index 90fb6bb0680..59fbb3bdfd5 100644 Binary files a/public/images/pokemon/variant/250_3.png and b/public/images/pokemon/variant/250_3.png differ diff --git a/public/images/pokemon/variant/257_3.png b/public/images/pokemon/variant/257_3.png index 8242580aaec..62c4b6589a3 100644 Binary files a/public/images/pokemon/variant/257_3.png and b/public/images/pokemon/variant/257_3.png differ diff --git a/public/images/pokemon/variant/291_1.png b/public/images/pokemon/variant/291_1.png index fea63c6c27c..30cae5bb507 100644 Binary files a/public/images/pokemon/variant/291_1.png and b/public/images/pokemon/variant/291_1.png differ diff --git a/public/images/pokemon/variant/291_2.png b/public/images/pokemon/variant/291_2.png index b7b771acb30..ccbe82a1201 100644 Binary files a/public/images/pokemon/variant/291_2.png and b/public/images/pokemon/variant/291_2.png differ diff --git a/public/images/pokemon/variant/291_3.png b/public/images/pokemon/variant/291_3.png index 72d28c371f2..26ea5d3cf4d 100644 Binary files a/public/images/pokemon/variant/291_3.png and b/public/images/pokemon/variant/291_3.png differ diff --git a/public/images/pokemon/variant/292_1.png b/public/images/pokemon/variant/292_1.png index 118bfe79542..3f4ec4df742 100644 Binary files a/public/images/pokemon/variant/292_1.png and b/public/images/pokemon/variant/292_1.png differ diff --git a/public/images/pokemon/variant/292_3.png b/public/images/pokemon/variant/292_3.png index 6b5f128fdd6..989d28d017a 100644 Binary files a/public/images/pokemon/variant/292_3.png and b/public/images/pokemon/variant/292_3.png differ diff --git a/public/images/pokemon/variant/298_2.png b/public/images/pokemon/variant/298_2.png index b48ebec70ad..f071c40c69a 100644 Binary files a/public/images/pokemon/variant/298_2.png and b/public/images/pokemon/variant/298_2.png differ diff --git a/public/images/pokemon/variant/298_3.png b/public/images/pokemon/variant/298_3.png index 137cee603a7..5dbf9b03b31 100644 Binary files a/public/images/pokemon/variant/298_3.png and b/public/images/pokemon/variant/298_3.png differ diff --git a/public/images/pokemon/variant/2_2.png b/public/images/pokemon/variant/2_2.png index 2477646fef4..b12bd60bd23 100644 Binary files a/public/images/pokemon/variant/2_2.png and b/public/images/pokemon/variant/2_2.png differ diff --git a/public/images/pokemon/variant/3-mega_2.png b/public/images/pokemon/variant/3-mega_2.png index 037e983a030..ddc2bd976ad 100644 Binary files a/public/images/pokemon/variant/3-mega_2.png and b/public/images/pokemon/variant/3-mega_2.png differ diff --git a/public/images/pokemon/variant/3-mega_3.png b/public/images/pokemon/variant/3-mega_3.png index 6a967466ac1..56945d383e6 100644 Binary files a/public/images/pokemon/variant/3-mega_3.png and b/public/images/pokemon/variant/3-mega_3.png differ diff --git a/public/images/pokemon/variant/308_2.png b/public/images/pokemon/variant/308_2.png index 42a11414659..0c13b01d575 100644 Binary files a/public/images/pokemon/variant/308_2.png and b/public/images/pokemon/variant/308_2.png differ diff --git a/public/images/pokemon/variant/31_1.png b/public/images/pokemon/variant/31_1.png index d471b062136..d968477a946 100644 Binary files a/public/images/pokemon/variant/31_1.png and b/public/images/pokemon/variant/31_1.png differ diff --git a/public/images/pokemon/variant/329_3.png b/public/images/pokemon/variant/329_3.png index 45fad04068e..0530cfa7be3 100644 Binary files a/public/images/pokemon/variant/329_3.png and b/public/images/pokemon/variant/329_3.png differ diff --git a/public/images/pokemon/variant/334-mega_2.png b/public/images/pokemon/variant/334-mega_2.png index df2c3950f2c..f0304935968 100644 Binary files a/public/images/pokemon/variant/334-mega_2.png and b/public/images/pokemon/variant/334-mega_2.png differ diff --git a/public/images/pokemon/variant/334_2.png b/public/images/pokemon/variant/334_2.png index 94457f721e3..cf30637cf79 100644 Binary files a/public/images/pokemon/variant/334_2.png and b/public/images/pokemon/variant/334_2.png differ diff --git a/public/images/pokemon/variant/334_3.png b/public/images/pokemon/variant/334_3.png index 67fd670b3cb..b54ed45c264 100644 Binary files a/public/images/pokemon/variant/334_3.png and b/public/images/pokemon/variant/334_3.png differ diff --git a/public/images/pokemon/variant/335_2.png b/public/images/pokemon/variant/335_2.png index 6f7924b4b33..4baa18218ae 100644 Binary files a/public/images/pokemon/variant/335_2.png and b/public/images/pokemon/variant/335_2.png differ diff --git a/public/images/pokemon/variant/335_3.png b/public/images/pokemon/variant/335_3.png index 25ab4a00c09..c42ce4ce429 100644 Binary files a/public/images/pokemon/variant/335_3.png and b/public/images/pokemon/variant/335_3.png differ diff --git a/public/images/pokemon/variant/340_3.png b/public/images/pokemon/variant/340_3.png index 7ddb0768e02..24aede4bafe 100644 Binary files a/public/images/pokemon/variant/340_3.png and b/public/images/pokemon/variant/340_3.png differ diff --git a/public/images/pokemon/variant/341_2.png b/public/images/pokemon/variant/341_2.png index 71793d13d87..ff943eab93c 100644 Binary files a/public/images/pokemon/variant/341_2.png and b/public/images/pokemon/variant/341_2.png differ diff --git a/public/images/pokemon/variant/341_3.png b/public/images/pokemon/variant/341_3.png index b0e07fea033..ed4697d48b8 100644 Binary files a/public/images/pokemon/variant/341_3.png and b/public/images/pokemon/variant/341_3.png differ diff --git a/public/images/pokemon/variant/342_2.png b/public/images/pokemon/variant/342_2.png index 647d838c357..e1f8acaf8a6 100644 Binary files a/public/images/pokemon/variant/342_2.png and b/public/images/pokemon/variant/342_2.png differ diff --git a/public/images/pokemon/variant/342_3.png b/public/images/pokemon/variant/342_3.png index b4d8e18d8a8..2a10036b3f3 100644 Binary files a/public/images/pokemon/variant/342_3.png and b/public/images/pokemon/variant/342_3.png differ diff --git a/public/images/pokemon/variant/351-rainy_2.png b/public/images/pokemon/variant/351-rainy_2.png index 0b3f403c1ff..61cacb77d2e 100644 Binary files a/public/images/pokemon/variant/351-rainy_2.png and b/public/images/pokemon/variant/351-rainy_2.png differ diff --git a/public/images/pokemon/variant/351-rainy_3.png b/public/images/pokemon/variant/351-rainy_3.png index fc163d23ddf..c61be0fdd7d 100644 Binary files a/public/images/pokemon/variant/351-rainy_3.png and b/public/images/pokemon/variant/351-rainy_3.png differ diff --git a/public/images/pokemon/variant/351-sunny_2.png b/public/images/pokemon/variant/351-sunny_2.png index 6fc85cb6094..3815dd5fd8a 100644 Binary files a/public/images/pokemon/variant/351-sunny_2.png and b/public/images/pokemon/variant/351-sunny_2.png differ diff --git a/public/images/pokemon/variant/351-sunny_3.png b/public/images/pokemon/variant/351-sunny_3.png index e21f7627d1f..3627d88615e 100644 Binary files a/public/images/pokemon/variant/351-sunny_3.png and b/public/images/pokemon/variant/351-sunny_3.png differ diff --git a/public/images/pokemon/variant/351_2.png b/public/images/pokemon/variant/351_2.png index c9369b2bef2..30b88866823 100644 Binary files a/public/images/pokemon/variant/351_2.png and b/public/images/pokemon/variant/351_2.png differ diff --git a/public/images/pokemon/variant/351_3.png b/public/images/pokemon/variant/351_3.png index ba77333aa28..4e319855999 100644 Binary files a/public/images/pokemon/variant/351_3.png and b/public/images/pokemon/variant/351_3.png differ diff --git a/public/images/pokemon/variant/358_1.png b/public/images/pokemon/variant/358_1.png index 369b9828261..ddbe5e00908 100644 Binary files a/public/images/pokemon/variant/358_1.png and b/public/images/pokemon/variant/358_1.png differ diff --git a/public/images/pokemon/variant/35_3.png b/public/images/pokemon/variant/35_3.png index 90883d9b2b8..68a8194fa0d 100644 Binary files a/public/images/pokemon/variant/35_3.png and b/public/images/pokemon/variant/35_3.png differ diff --git a/public/images/pokemon/variant/362_2.png b/public/images/pokemon/variant/362_2.png index 1775f62a68f..3c21d176209 100644 Binary files a/public/images/pokemon/variant/362_2.png and b/public/images/pokemon/variant/362_2.png differ diff --git a/public/images/pokemon/variant/362_3.png b/public/images/pokemon/variant/362_3.png index 7b0d15894db..72b598a2d7b 100644 Binary files a/public/images/pokemon/variant/362_3.png and b/public/images/pokemon/variant/362_3.png differ diff --git a/public/images/pokemon/variant/373-mega_2.png b/public/images/pokemon/variant/373-mega_2.png index 2ddf5e07af5..ebde7060535 100644 Binary files a/public/images/pokemon/variant/373-mega_2.png and b/public/images/pokemon/variant/373-mega_2.png differ diff --git a/public/images/pokemon/variant/373-mega_3.png b/public/images/pokemon/variant/373-mega_3.png index 50cc1517a17..8fd93cbc0d2 100644 Binary files a/public/images/pokemon/variant/373-mega_3.png and b/public/images/pokemon/variant/373-mega_3.png differ diff --git a/public/images/pokemon/variant/384-mega_2.png b/public/images/pokemon/variant/384-mega_2.png index a834d31418c..370cea05f93 100644 Binary files a/public/images/pokemon/variant/384-mega_2.png and b/public/images/pokemon/variant/384-mega_2.png differ diff --git a/public/images/pokemon/variant/4080_1.png b/public/images/pokemon/variant/4080_1.png index 2508ea4c736..73ccbebdfa4 100644 Binary files a/public/images/pokemon/variant/4080_1.png and b/public/images/pokemon/variant/4080_1.png differ diff --git a/public/images/pokemon/variant/412-sandy_1.png b/public/images/pokemon/variant/412-sandy_1.png index d41a9cde14b..ec173a73749 100644 Binary files a/public/images/pokemon/variant/412-sandy_1.png and b/public/images/pokemon/variant/412-sandy_1.png differ diff --git a/public/images/pokemon/variant/412-sandy_2.png b/public/images/pokemon/variant/412-sandy_2.png index cd0ac94b35a..fb8d7724760 100644 Binary files a/public/images/pokemon/variant/412-sandy_2.png and b/public/images/pokemon/variant/412-sandy_2.png differ diff --git a/public/images/pokemon/variant/412-sandy_3.png b/public/images/pokemon/variant/412-sandy_3.png index f416fa6e46d..75ae220614c 100644 Binary files a/public/images/pokemon/variant/412-sandy_3.png and b/public/images/pokemon/variant/412-sandy_3.png differ diff --git a/public/images/pokemon/variant/413-plant_1.png b/public/images/pokemon/variant/413-plant_1.png index 367ee3bffee..59de20445ab 100644 Binary files a/public/images/pokemon/variant/413-plant_1.png and b/public/images/pokemon/variant/413-plant_1.png differ diff --git a/public/images/pokemon/variant/413-plant_2.png b/public/images/pokemon/variant/413-plant_2.png index 5b89b029bee..8e29a3b0549 100644 Binary files a/public/images/pokemon/variant/413-plant_2.png and b/public/images/pokemon/variant/413-plant_2.png differ diff --git a/public/images/pokemon/variant/413-plant_3.png b/public/images/pokemon/variant/413-plant_3.png index 21b9d30a5d5..cd0dc42bae1 100644 Binary files a/public/images/pokemon/variant/413-plant_3.png and b/public/images/pokemon/variant/413-plant_3.png differ diff --git a/public/images/pokemon/variant/4144_2.png b/public/images/pokemon/variant/4144_2.png index 873b08c4247..24453364a60 100644 Binary files a/public/images/pokemon/variant/4144_2.png and b/public/images/pokemon/variant/4144_2.png differ diff --git a/public/images/pokemon/variant/4144_3.png b/public/images/pokemon/variant/4144_3.png index 164e7dc0d25..5a55039463b 100644 Binary files a/public/images/pokemon/variant/4144_3.png and b/public/images/pokemon/variant/4144_3.png differ diff --git a/public/images/pokemon/variant/4199_1.png b/public/images/pokemon/variant/4199_1.png index e2fe544a480..85f535c8256 100644 Binary files a/public/images/pokemon/variant/4199_1.png and b/public/images/pokemon/variant/4199_1.png differ diff --git a/public/images/pokemon/variant/440_3.png b/public/images/pokemon/variant/440_3.png index 6c0086945e1..b6b8f2266fa 100644 Binary files a/public/images/pokemon/variant/440_3.png and b/public/images/pokemon/variant/440_3.png differ diff --git a/public/images/pokemon/variant/466_3.png b/public/images/pokemon/variant/466_3.png index 5267e3bf87a..146c4707a4f 100644 Binary files a/public/images/pokemon/variant/466_3.png and b/public/images/pokemon/variant/466_3.png differ diff --git a/public/images/pokemon/variant/469_2.png b/public/images/pokemon/variant/469_2.png index f7ea79b0509..82874a3978c 100644 Binary files a/public/images/pokemon/variant/469_2.png and b/public/images/pokemon/variant/469_2.png differ diff --git a/public/images/pokemon/variant/469_3.png b/public/images/pokemon/variant/469_3.png index 9c724123f37..a76d0d84f5c 100644 Binary files a/public/images/pokemon/variant/469_3.png and b/public/images/pokemon/variant/469_3.png differ diff --git a/public/images/pokemon/variant/471_2.png b/public/images/pokemon/variant/471_2.png index 62be05287c6..d93266b56db 100644 Binary files a/public/images/pokemon/variant/471_2.png and b/public/images/pokemon/variant/471_2.png differ diff --git a/public/images/pokemon/variant/471_3.png b/public/images/pokemon/variant/471_3.png index 0d2f5e2cfee..12df5891dc2 100644 Binary files a/public/images/pokemon/variant/471_3.png and b/public/images/pokemon/variant/471_3.png differ diff --git a/public/images/pokemon/variant/478_2.png b/public/images/pokemon/variant/478_2.png index 152b51b0b38..65131a6ecfa 100644 Binary files a/public/images/pokemon/variant/478_2.png and b/public/images/pokemon/variant/478_2.png differ diff --git a/public/images/pokemon/variant/492-land_2.png b/public/images/pokemon/variant/492-land_2.png index 823678f5a05..cf51ec294ab 100644 Binary files a/public/images/pokemon/variant/492-land_2.png and b/public/images/pokemon/variant/492-land_2.png differ diff --git a/public/images/pokemon/variant/526_2.png b/public/images/pokemon/variant/526_2.png index cf095fcc45b..68df77dd22d 100644 Binary files a/public/images/pokemon/variant/526_2.png and b/public/images/pokemon/variant/526_2.png differ diff --git a/public/images/pokemon/variant/526_3.png b/public/images/pokemon/variant/526_3.png index c365c379910..f347af32030 100644 Binary files a/public/images/pokemon/variant/526_3.png and b/public/images/pokemon/variant/526_3.png differ diff --git a/public/images/pokemon/variant/529_2.png b/public/images/pokemon/variant/529_2.png index 049f45bd07b..59e8dc11094 100644 Binary files a/public/images/pokemon/variant/529_2.png and b/public/images/pokemon/variant/529_2.png differ diff --git a/public/images/pokemon/variant/529_3.png b/public/images/pokemon/variant/529_3.png index de34a2b8cd2..ad7c95bbcc2 100644 Binary files a/public/images/pokemon/variant/529_3.png and b/public/images/pokemon/variant/529_3.png differ diff --git a/public/images/pokemon/variant/530_2.png b/public/images/pokemon/variant/530_2.png index 00e9c7b2103..2fb768d6df4 100644 Binary files a/public/images/pokemon/variant/530_2.png and b/public/images/pokemon/variant/530_2.png differ diff --git a/public/images/pokemon/variant/530_3.png b/public/images/pokemon/variant/530_3.png index 6772ad1ea1c..f92b642163c 100644 Binary files a/public/images/pokemon/variant/530_3.png and b/public/images/pokemon/variant/530_3.png differ diff --git a/public/images/pokemon/variant/539_2.png b/public/images/pokemon/variant/539_2.png index 7db9919d8b5..7b738217ed4 100644 Binary files a/public/images/pokemon/variant/539_2.png and b/public/images/pokemon/variant/539_2.png differ diff --git a/public/images/pokemon/variant/539_3.png b/public/images/pokemon/variant/539_3.png index 9b025a27e9f..8f581d1348e 100644 Binary files a/public/images/pokemon/variant/539_3.png and b/public/images/pokemon/variant/539_3.png differ diff --git a/public/images/pokemon/variant/543_3.png b/public/images/pokemon/variant/543_3.png index f10317e806d..7877d5e39b4 100644 Binary files a/public/images/pokemon/variant/543_3.png and b/public/images/pokemon/variant/543_3.png differ diff --git a/public/images/pokemon/variant/544_3.png b/public/images/pokemon/variant/544_3.png index 921a667741d..a0d810c4a29 100644 Binary files a/public/images/pokemon/variant/544_3.png and b/public/images/pokemon/variant/544_3.png differ diff --git a/public/images/pokemon/variant/549_3.png b/public/images/pokemon/variant/549_3.png index b7f2244e373..fffb5b9848a 100644 Binary files a/public/images/pokemon/variant/549_3.png and b/public/images/pokemon/variant/549_3.png differ diff --git a/public/images/pokemon/variant/568_2.png b/public/images/pokemon/variant/568_2.png index ed4c0d50ead..5ec25e6e8a6 100644 Binary files a/public/images/pokemon/variant/568_2.png and b/public/images/pokemon/variant/568_2.png differ diff --git a/public/images/pokemon/variant/568_3.png b/public/images/pokemon/variant/568_3.png index d499797bc92..cc991834a42 100644 Binary files a/public/images/pokemon/variant/568_3.png and b/public/images/pokemon/variant/568_3.png differ diff --git a/public/images/pokemon/variant/56_2.png b/public/images/pokemon/variant/56_2.png index a4d69550157..8f9b8f258e1 100644 Binary files a/public/images/pokemon/variant/56_2.png and b/public/images/pokemon/variant/56_2.png differ diff --git a/public/images/pokemon/variant/56_3.png b/public/images/pokemon/variant/56_3.png index 0c056ccdac9..ee5508c861a 100644 Binary files a/public/images/pokemon/variant/56_3.png and b/public/images/pokemon/variant/56_3.png differ diff --git a/public/images/pokemon/variant/57_1.png b/public/images/pokemon/variant/57_1.png index 73db688c129..8ea65328703 100644 Binary files a/public/images/pokemon/variant/57_1.png and b/public/images/pokemon/variant/57_1.png differ diff --git a/public/images/pokemon/variant/57_2.png b/public/images/pokemon/variant/57_2.png index b2edfa6cea7..6786b956a3c 100644 Binary files a/public/images/pokemon/variant/57_2.png and b/public/images/pokemon/variant/57_2.png differ diff --git a/public/images/pokemon/variant/57_3.png b/public/images/pokemon/variant/57_3.png index 9ec02aa03a9..720ad1f3529 100644 Binary files a/public/images/pokemon/variant/57_3.png and b/public/images/pokemon/variant/57_3.png differ diff --git a/public/images/pokemon/variant/585-autumn_1.png b/public/images/pokemon/variant/585-autumn_1.png index bc5c1424226..8edc0f0f152 100644 Binary files a/public/images/pokemon/variant/585-autumn_1.png and b/public/images/pokemon/variant/585-autumn_1.png differ diff --git a/public/images/pokemon/variant/585-spring_1.png b/public/images/pokemon/variant/585-spring_1.png index 1f154916dcd..7ab83601cf6 100644 Binary files a/public/images/pokemon/variant/585-spring_1.png and b/public/images/pokemon/variant/585-spring_1.png differ diff --git a/public/images/pokemon/variant/585-summer_1.png b/public/images/pokemon/variant/585-summer_1.png index 59b40b7bfef..e4204ea7dd8 100644 Binary files a/public/images/pokemon/variant/585-summer_1.png and b/public/images/pokemon/variant/585-summer_1.png differ diff --git a/public/images/pokemon/variant/585-winter_1.png b/public/images/pokemon/variant/585-winter_1.png index b296e059d1c..e5a0993319c 100644 Binary files a/public/images/pokemon/variant/585-winter_1.png and b/public/images/pokemon/variant/585-winter_1.png differ diff --git a/public/images/pokemon/variant/592_3.png b/public/images/pokemon/variant/592_3.png index e7694c4c5cf..aaded0d9d7e 100644 Binary files a/public/images/pokemon/variant/592_3.png and b/public/images/pokemon/variant/592_3.png differ diff --git a/public/images/pokemon/variant/594_3.png b/public/images/pokemon/variant/594_3.png index 5db7ef43b86..91f730c416f 100644 Binary files a/public/images/pokemon/variant/594_3.png and b/public/images/pokemon/variant/594_3.png differ diff --git a/public/images/pokemon/variant/631_2.png b/public/images/pokemon/variant/631_2.png index 846ab3c59b5..e2f0d2b9252 100644 Binary files a/public/images/pokemon/variant/631_2.png and b/public/images/pokemon/variant/631_2.png differ diff --git a/public/images/pokemon/variant/631_3.png b/public/images/pokemon/variant/631_3.png index bd271ebda23..4bcb055fbe5 100644 Binary files a/public/images/pokemon/variant/631_3.png and b/public/images/pokemon/variant/631_3.png differ diff --git a/public/images/pokemon/variant/666-continental_3.png b/public/images/pokemon/variant/666-continental_3.png index 8052f0360f5..24e4c9ab37c 100644 Binary files a/public/images/pokemon/variant/666-continental_3.png and b/public/images/pokemon/variant/666-continental_3.png differ diff --git a/public/images/pokemon/variant/666-fancy_2.png b/public/images/pokemon/variant/666-fancy_2.png index 68e535cf3a1..100ba90d7e4 100644 Binary files a/public/images/pokemon/variant/666-fancy_2.png and b/public/images/pokemon/variant/666-fancy_2.png differ diff --git a/public/images/pokemon/variant/666-fancy_3.png b/public/images/pokemon/variant/666-fancy_3.png index 2accdb4f486..a8453d6cbce 100644 Binary files a/public/images/pokemon/variant/666-fancy_3.png and b/public/images/pokemon/variant/666-fancy_3.png differ diff --git a/public/images/pokemon/variant/666-poke-ball_3.png b/public/images/pokemon/variant/666-poke-ball_3.png index 64a072a7cbe..e6056246fce 100644 Binary files a/public/images/pokemon/variant/666-poke-ball_3.png and b/public/images/pokemon/variant/666-poke-ball_3.png differ diff --git a/public/images/pokemon/variant/666-river_2.png b/public/images/pokemon/variant/666-river_2.png index 9b02df92a91..a5a65f4933b 100644 Binary files a/public/images/pokemon/variant/666-river_2.png and b/public/images/pokemon/variant/666-river_2.png differ diff --git a/public/images/pokemon/variant/669-blue_2.png b/public/images/pokemon/variant/669-blue_2.png index b2f9ac59598..32ecda80b8d 100644 Binary files a/public/images/pokemon/variant/669-blue_2.png and b/public/images/pokemon/variant/669-blue_2.png differ diff --git a/public/images/pokemon/variant/669-blue_3.png b/public/images/pokemon/variant/669-blue_3.png index 8cc36b72d06..8ce437a4442 100644 Binary files a/public/images/pokemon/variant/669-blue_3.png and b/public/images/pokemon/variant/669-blue_3.png differ diff --git a/public/images/pokemon/variant/669-orange_2.png b/public/images/pokemon/variant/669-orange_2.png index 3131bba78ea..fe6a2ccbf7d 100644 Binary files a/public/images/pokemon/variant/669-orange_2.png and b/public/images/pokemon/variant/669-orange_2.png differ diff --git a/public/images/pokemon/variant/669-red_2.png b/public/images/pokemon/variant/669-red_2.png index b898c298fbf..5d30b38b1d4 100644 Binary files a/public/images/pokemon/variant/669-red_2.png and b/public/images/pokemon/variant/669-red_2.png differ diff --git a/public/images/pokemon/variant/669-yellow_2.png b/public/images/pokemon/variant/669-yellow_2.png index 37e8d8726a9..5811dfd493b 100644 Binary files a/public/images/pokemon/variant/669-yellow_2.png and b/public/images/pokemon/variant/669-yellow_2.png differ diff --git a/public/images/pokemon/variant/670-blue_2.png b/public/images/pokemon/variant/670-blue_2.png index 1362ccbd2e5..73e797d7c28 100644 Binary files a/public/images/pokemon/variant/670-blue_2.png and b/public/images/pokemon/variant/670-blue_2.png differ diff --git a/public/images/pokemon/variant/670-blue_3.png b/public/images/pokemon/variant/670-blue_3.png index 881ca7f208f..e9f9322eaf1 100644 Binary files a/public/images/pokemon/variant/670-blue_3.png and b/public/images/pokemon/variant/670-blue_3.png differ diff --git a/public/images/pokemon/variant/670-orange_2.png b/public/images/pokemon/variant/670-orange_2.png index 80ccbdf030a..1144b3deb5e 100644 Binary files a/public/images/pokemon/variant/670-orange_2.png and b/public/images/pokemon/variant/670-orange_2.png differ diff --git a/public/images/pokemon/variant/670-orange_3.png b/public/images/pokemon/variant/670-orange_3.png index 0c579de4ed8..585031e7c26 100644 Binary files a/public/images/pokemon/variant/670-orange_3.png and b/public/images/pokemon/variant/670-orange_3.png differ diff --git a/public/images/pokemon/variant/670-red_2.png b/public/images/pokemon/variant/670-red_2.png index 7c7618848b9..5a53a3fc8a1 100644 Binary files a/public/images/pokemon/variant/670-red_2.png and b/public/images/pokemon/variant/670-red_2.png differ diff --git a/public/images/pokemon/variant/670-red_3.png b/public/images/pokemon/variant/670-red_3.png index 95bea559c8d..4774c9e8012 100644 Binary files a/public/images/pokemon/variant/670-red_3.png and b/public/images/pokemon/variant/670-red_3.png differ diff --git a/public/images/pokemon/variant/670-white_2.png b/public/images/pokemon/variant/670-white_2.png index 303905201d8..f5cdefbeef6 100644 Binary files a/public/images/pokemon/variant/670-white_2.png and b/public/images/pokemon/variant/670-white_2.png differ diff --git a/public/images/pokemon/variant/670-white_3.png b/public/images/pokemon/variant/670-white_3.png index ce9479353f4..051eea69c8f 100644 Binary files a/public/images/pokemon/variant/670-white_3.png and b/public/images/pokemon/variant/670-white_3.png differ diff --git a/public/images/pokemon/variant/670-yellow_2.png b/public/images/pokemon/variant/670-yellow_2.png index e1866464222..9a02754ed75 100644 Binary files a/public/images/pokemon/variant/670-yellow_2.png and b/public/images/pokemon/variant/670-yellow_2.png differ diff --git a/public/images/pokemon/variant/670-yellow_3.png b/public/images/pokemon/variant/670-yellow_3.png index af6e6d83f43..86464b7c97e 100644 Binary files a/public/images/pokemon/variant/670-yellow_3.png and b/public/images/pokemon/variant/670-yellow_3.png differ diff --git a/public/images/pokemon/variant/671-blue_3.png b/public/images/pokemon/variant/671-blue_3.png index 8d755b76a7c..fb57dab589e 100644 Binary files a/public/images/pokemon/variant/671-blue_3.png and b/public/images/pokemon/variant/671-blue_3.png differ diff --git a/public/images/pokemon/variant/671-red_3.png b/public/images/pokemon/variant/671-red_3.png index ad3656d2b62..9daddb10dc1 100644 Binary files a/public/images/pokemon/variant/671-red_3.png and b/public/images/pokemon/variant/671-red_3.png differ diff --git a/public/images/pokemon/variant/671-white_3.png b/public/images/pokemon/variant/671-white_3.png index bab8091ae50..3c024e09b8d 100644 Binary files a/public/images/pokemon/variant/671-white_3.png and b/public/images/pokemon/variant/671-white_3.png differ diff --git a/public/images/pokemon/variant/671-yellow_3.png b/public/images/pokemon/variant/671-yellow_3.png index f518af40869..dab7a758d81 100644 Binary files a/public/images/pokemon/variant/671-yellow_3.png and b/public/images/pokemon/variant/671-yellow_3.png differ diff --git a/public/images/pokemon/variant/672_3.png b/public/images/pokemon/variant/672_3.png index 47e1401b99f..02d2fe283b9 100644 Binary files a/public/images/pokemon/variant/672_3.png and b/public/images/pokemon/variant/672_3.png differ diff --git a/public/images/pokemon/variant/696_3.png b/public/images/pokemon/variant/696_3.png index d053158281d..6eab4cd29b3 100644 Binary files a/public/images/pokemon/variant/696_3.png and b/public/images/pokemon/variant/696_3.png differ diff --git a/public/images/pokemon/variant/697_3.png b/public/images/pokemon/variant/697_3.png index c6dbd843eb0..055f35c71e0 100644 Binary files a/public/images/pokemon/variant/697_3.png and b/public/images/pokemon/variant/697_3.png differ diff --git a/public/images/pokemon/variant/69_2.png b/public/images/pokemon/variant/69_2.png index 2f2a713de27..8ed462e7c9b 100644 Binary files a/public/images/pokemon/variant/69_2.png and b/public/images/pokemon/variant/69_2.png differ diff --git a/public/images/pokemon/variant/69_3.png b/public/images/pokemon/variant/69_3.png index 537d1379e1b..6e37bdf8c33 100644 Binary files a/public/images/pokemon/variant/69_3.png and b/public/images/pokemon/variant/69_3.png differ diff --git a/public/images/pokemon/variant/715_2.png b/public/images/pokemon/variant/715_2.png index 33534d0e77e..fe80cacfd31 100644 Binary files a/public/images/pokemon/variant/715_2.png and b/public/images/pokemon/variant/715_2.png differ diff --git a/public/images/pokemon/variant/715_3.png b/public/images/pokemon/variant/715_3.png index 77b4b1d5006..e48057a11ff 100644 Binary files a/public/images/pokemon/variant/715_3.png and b/public/images/pokemon/variant/715_3.png differ diff --git a/public/images/pokemon/variant/742_2.png b/public/images/pokemon/variant/742_2.png index 43103181126..32dc5593c55 100644 Binary files a/public/images/pokemon/variant/742_2.png and b/public/images/pokemon/variant/742_2.png differ diff --git a/public/images/pokemon/variant/742_3.png b/public/images/pokemon/variant/742_3.png index 88002632e61..599b1598002 100644 Binary files a/public/images/pokemon/variant/742_3.png and b/public/images/pokemon/variant/742_3.png differ diff --git a/public/images/pokemon/variant/743_2.png b/public/images/pokemon/variant/743_2.png index fe5fbfae5d3..fb83af131eb 100644 Binary files a/public/images/pokemon/variant/743_2.png and b/public/images/pokemon/variant/743_2.png differ diff --git a/public/images/pokemon/variant/743_3.png b/public/images/pokemon/variant/743_3.png index 36a3ae6513b..7a36dc4f89c 100644 Binary files a/public/images/pokemon/variant/743_3.png and b/public/images/pokemon/variant/743_3.png differ diff --git a/public/images/pokemon/variant/756_3.png b/public/images/pokemon/variant/756_3.png index c9277b4e307..f4a072a1b2b 100644 Binary files a/public/images/pokemon/variant/756_3.png and b/public/images/pokemon/variant/756_3.png differ diff --git a/public/images/pokemon/variant/791_1.png b/public/images/pokemon/variant/791_1.png index d5ccdb14e90..719e1b34c59 100644 Binary files a/public/images/pokemon/variant/791_1.png and b/public/images/pokemon/variant/791_1.png differ diff --git a/public/images/pokemon/variant/7_2.png b/public/images/pokemon/variant/7_2.png index 13d2f17b641..63321bf71c2 100644 Binary files a/public/images/pokemon/variant/7_2.png and b/public/images/pokemon/variant/7_2.png differ diff --git a/public/images/pokemon/variant/7_3.png b/public/images/pokemon/variant/7_3.png index f15d95d100f..b4adaff2f54 100644 Binary files a/public/images/pokemon/variant/7_3.png and b/public/images/pokemon/variant/7_3.png differ diff --git a/public/images/pokemon/variant/823-gigantamax_2.png b/public/images/pokemon/variant/823-gigantamax_2.png index 9bd4dd183a3..eecb3695378 100644 Binary files a/public/images/pokemon/variant/823-gigantamax_2.png and b/public/images/pokemon/variant/823-gigantamax_2.png differ diff --git a/public/images/pokemon/variant/823-gigantamax_3.png b/public/images/pokemon/variant/823-gigantamax_3.png index 6e51618a272..0af4712fdf7 100644 Binary files a/public/images/pokemon/variant/823-gigantamax_3.png and b/public/images/pokemon/variant/823-gigantamax_3.png differ diff --git a/public/images/pokemon/variant/836_2.png b/public/images/pokemon/variant/836_2.png index 7f584eeb5ad..303975e7e58 100644 Binary files a/public/images/pokemon/variant/836_2.png and b/public/images/pokemon/variant/836_2.png differ diff --git a/public/images/pokemon/variant/836_3.png b/public/images/pokemon/variant/836_3.png index 22418aaee07..95ae2990ed2 100644 Binary files a/public/images/pokemon/variant/836_3.png and b/public/images/pokemon/variant/836_3.png differ diff --git a/public/images/pokemon/variant/83_3.png b/public/images/pokemon/variant/83_3.png index 8a2f0d763b1..eb6604c6299 100644 Binary files a/public/images/pokemon/variant/83_3.png and b/public/images/pokemon/variant/83_3.png differ diff --git a/public/images/pokemon/variant/857_2.png b/public/images/pokemon/variant/857_2.png index 6395590c758..9273ef2a097 100644 Binary files a/public/images/pokemon/variant/857_2.png and b/public/images/pokemon/variant/857_2.png differ diff --git a/public/images/pokemon/variant/857_3.png b/public/images/pokemon/variant/857_3.png index c2a1b95c337..dc3277bcc2c 100644 Binary files a/public/images/pokemon/variant/857_3.png and b/public/images/pokemon/variant/857_3.png differ diff --git a/public/images/pokemon/variant/859_2.png b/public/images/pokemon/variant/859_2.png index 111e51b0892..82e8be45c39 100644 Binary files a/public/images/pokemon/variant/859_2.png and b/public/images/pokemon/variant/859_2.png differ diff --git a/public/images/pokemon/variant/859_3.png b/public/images/pokemon/variant/859_3.png index 1c6888a0f95..800063f08aa 100644 Binary files a/public/images/pokemon/variant/859_3.png and b/public/images/pokemon/variant/859_3.png differ diff --git a/public/images/pokemon/variant/862_2.png b/public/images/pokemon/variant/862_2.png index d006a389c11..4c52d8a260f 100644 Binary files a/public/images/pokemon/variant/862_2.png and b/public/images/pokemon/variant/862_2.png differ diff --git a/public/images/pokemon/variant/862_3.png b/public/images/pokemon/variant/862_3.png index 1d9240d85e6..170ae08d72d 100644 Binary files a/public/images/pokemon/variant/862_3.png and b/public/images/pokemon/variant/862_3.png differ diff --git a/public/images/pokemon/variant/887_1.png b/public/images/pokemon/variant/887_1.png index 8348e95c664..505ac265740 100644 Binary files a/public/images/pokemon/variant/887_1.png and b/public/images/pokemon/variant/887_1.png differ diff --git a/public/images/pokemon/variant/890-eternamax_2.png b/public/images/pokemon/variant/890-eternamax_2.png index b5aab00c241..2327900b971 100644 Binary files a/public/images/pokemon/variant/890-eternamax_2.png and b/public/images/pokemon/variant/890-eternamax_2.png differ diff --git a/public/images/pokemon/variant/890-eternamax_3.png b/public/images/pokemon/variant/890-eternamax_3.png index 29a3d84c0d6..140837cfbd0 100644 Binary files a/public/images/pokemon/variant/890-eternamax_3.png and b/public/images/pokemon/variant/890-eternamax_3.png differ diff --git a/public/images/pokemon/variant/890_2.png b/public/images/pokemon/variant/890_2.png index 46d2a4590a4..936f13cb6b2 100644 Binary files a/public/images/pokemon/variant/890_2.png and b/public/images/pokemon/variant/890_2.png differ diff --git a/public/images/pokemon/variant/890_3.png b/public/images/pokemon/variant/890_3.png index 635272e7e17..683658d9b86 100644 Binary files a/public/images/pokemon/variant/890_3.png and b/public/images/pokemon/variant/890_3.png differ diff --git a/public/images/pokemon/variant/897_1.png b/public/images/pokemon/variant/897_1.png index 74e793e31fa..9519aadd702 100644 Binary files a/public/images/pokemon/variant/897_1.png and b/public/images/pokemon/variant/897_1.png differ diff --git a/public/images/pokemon/variant/8_2.png b/public/images/pokemon/variant/8_2.png index c65b2bf1aae..06067c8e4f2 100644 Binary files a/public/images/pokemon/variant/8_2.png and b/public/images/pokemon/variant/8_2.png differ diff --git a/public/images/pokemon/variant/8_3.png b/public/images/pokemon/variant/8_3.png index 55e8869cbc7..5c32863d7df 100644 Binary files a/public/images/pokemon/variant/8_3.png and b/public/images/pokemon/variant/8_3.png differ diff --git a/public/images/pokemon/variant/9-gigantamax_3.png b/public/images/pokemon/variant/9-gigantamax_3.png index 1a12ccc6f00..54819b05e7e 100644 Binary files a/public/images/pokemon/variant/9-gigantamax_3.png and b/public/images/pokemon/variant/9-gigantamax_3.png differ diff --git a/public/images/pokemon/variant/9-mega_2.png b/public/images/pokemon/variant/9-mega_2.png index c3c06c1eca7..0a56f1a3a68 100644 Binary files a/public/images/pokemon/variant/9-mega_2.png and b/public/images/pokemon/variant/9-mega_2.png differ diff --git a/public/images/pokemon/variant/9-mega_3.png b/public/images/pokemon/variant/9-mega_3.png index d7a4b6f9140..606f18d6337 100644 Binary files a/public/images/pokemon/variant/9-mega_3.png and b/public/images/pokemon/variant/9-mega_3.png differ diff --git a/public/images/pokemon/variant/909_2.png b/public/images/pokemon/variant/909_2.png index 87d636295f9..142d46abb95 100644 Binary files a/public/images/pokemon/variant/909_2.png and b/public/images/pokemon/variant/909_2.png differ diff --git a/public/images/pokemon/variant/909_3.png b/public/images/pokemon/variant/909_3.png index 9681aebe268..ec49f615edf 100644 Binary files a/public/images/pokemon/variant/909_3.png and b/public/images/pokemon/variant/909_3.png differ diff --git a/public/images/pokemon/variant/910_2.png b/public/images/pokemon/variant/910_2.png index 616d1380f64..14fcf13f9a9 100644 Binary files a/public/images/pokemon/variant/910_2.png and b/public/images/pokemon/variant/910_2.png differ diff --git a/public/images/pokemon/variant/910_3.png b/public/images/pokemon/variant/910_3.png index 5c5c051ecd5..8a9dfa4f8b0 100644 Binary files a/public/images/pokemon/variant/910_3.png and b/public/images/pokemon/variant/910_3.png differ diff --git a/public/images/pokemon/variant/913_2.png b/public/images/pokemon/variant/913_2.png index 0af21bab11c..ab5e42c3b73 100644 Binary files a/public/images/pokemon/variant/913_2.png and b/public/images/pokemon/variant/913_2.png differ diff --git a/public/images/pokemon/variant/913_3.png b/public/images/pokemon/variant/913_3.png index 42398fe12d6..3c29e2b8f91 100644 Binary files a/public/images/pokemon/variant/913_3.png and b/public/images/pokemon/variant/913_3.png differ diff --git a/public/images/pokemon/variant/914_2.png b/public/images/pokemon/variant/914_2.png index 672e45f026a..d1db92c1e8a 100644 Binary files a/public/images/pokemon/variant/914_2.png and b/public/images/pokemon/variant/914_2.png differ diff --git a/public/images/pokemon/variant/920_1.png b/public/images/pokemon/variant/920_1.png index 40d07ac6111..a0fcd5b4a7f 100644 Binary files a/public/images/pokemon/variant/920_1.png and b/public/images/pokemon/variant/920_1.png differ diff --git a/public/images/pokemon/variant/920_2.png b/public/images/pokemon/variant/920_2.png index ac8dc6ba169..71b9a3b7651 100644 Binary files a/public/images/pokemon/variant/920_2.png and b/public/images/pokemon/variant/920_2.png differ diff --git a/public/images/pokemon/variant/920_3.png b/public/images/pokemon/variant/920_3.png index b46cfe90aa6..0db8dad3cd9 100644 Binary files a/public/images/pokemon/variant/920_3.png and b/public/images/pokemon/variant/920_3.png differ diff --git a/public/images/pokemon/variant/92_1.png b/public/images/pokemon/variant/92_1.png index a47bf334486..23f4da8ef6a 100644 Binary files a/public/images/pokemon/variant/92_1.png and b/public/images/pokemon/variant/92_1.png differ diff --git a/public/images/pokemon/variant/92_2.png b/public/images/pokemon/variant/92_2.png index a6bff69023f..383151d9a72 100644 Binary files a/public/images/pokemon/variant/92_2.png and b/public/images/pokemon/variant/92_2.png differ diff --git a/public/images/pokemon/variant/92_3.png b/public/images/pokemon/variant/92_3.png index 4839b2d4ce6..38684540b9f 100644 Binary files a/public/images/pokemon/variant/92_3.png and b/public/images/pokemon/variant/92_3.png differ diff --git a/public/images/pokemon/variant/94-gigantamax_2.png b/public/images/pokemon/variant/94-gigantamax_2.png index 008536206d1..f47846e421d 100644 Binary files a/public/images/pokemon/variant/94-gigantamax_2.png and b/public/images/pokemon/variant/94-gigantamax_2.png differ diff --git a/public/images/pokemon/variant/94-gigantamax_3.png b/public/images/pokemon/variant/94-gigantamax_3.png index 91247d2373b..bd571698edc 100644 Binary files a/public/images/pokemon/variant/94-gigantamax_3.png and b/public/images/pokemon/variant/94-gigantamax_3.png differ diff --git a/public/images/pokemon/variant/94-mega_1.png b/public/images/pokemon/variant/94-mega_1.png index 5484a0b7f61..c1b560477ac 100644 Binary files a/public/images/pokemon/variant/94-mega_1.png and b/public/images/pokemon/variant/94-mega_1.png differ diff --git a/public/images/pokemon/variant/94-mega_2.png b/public/images/pokemon/variant/94-mega_2.png index 12890419db2..79329baa6e9 100644 Binary files a/public/images/pokemon/variant/94-mega_2.png and b/public/images/pokemon/variant/94-mega_2.png differ diff --git a/public/images/pokemon/variant/94-mega_3.png b/public/images/pokemon/variant/94-mega_3.png index 7abb2c15a6f..0df495494b4 100644 Binary files a/public/images/pokemon/variant/94-mega_3.png and b/public/images/pokemon/variant/94-mega_3.png differ diff --git a/public/images/pokemon/variant/968_2.png b/public/images/pokemon/variant/968_2.png index 73a91ca6913..45c8f6b260a 100644 Binary files a/public/images/pokemon/variant/968_2.png and b/public/images/pokemon/variant/968_2.png differ diff --git a/public/images/pokemon/variant/968_3.png b/public/images/pokemon/variant/968_3.png index 10e4f06fc3f..73c87fbe249 100644 Binary files a/public/images/pokemon/variant/968_3.png and b/public/images/pokemon/variant/968_3.png differ diff --git a/public/images/pokemon/variant/973_1.png b/public/images/pokemon/variant/973_1.png index 38cdfed1b5a..bbf65d9d7fa 100644 Binary files a/public/images/pokemon/variant/973_1.png and b/public/images/pokemon/variant/973_1.png differ diff --git a/public/images/pokemon/variant/973_2.png b/public/images/pokemon/variant/973_2.png index 0e0ed775104..bbebe06afb0 100644 Binary files a/public/images/pokemon/variant/973_2.png and b/public/images/pokemon/variant/973_2.png differ diff --git a/public/images/pokemon/variant/973_3.png b/public/images/pokemon/variant/973_3.png index 07c09415b98..2f90e0cdbee 100644 Binary files a/public/images/pokemon/variant/973_3.png and b/public/images/pokemon/variant/973_3.png differ diff --git a/public/images/pokemon/variant/975_2.png b/public/images/pokemon/variant/975_2.png index a122990f639..bc174f1c824 100644 Binary files a/public/images/pokemon/variant/975_2.png and b/public/images/pokemon/variant/975_2.png differ diff --git a/public/images/pokemon/variant/975_3.png b/public/images/pokemon/variant/975_3.png index e1cb9773902..218ad884dde 100644 Binary files a/public/images/pokemon/variant/975_3.png and b/public/images/pokemon/variant/975_3.png differ diff --git a/public/images/pokemon/variant/978-curly_2.png b/public/images/pokemon/variant/978-curly_2.png index d579278625f..d29314bd6ea 100644 Binary files a/public/images/pokemon/variant/978-curly_2.png and b/public/images/pokemon/variant/978-curly_2.png differ diff --git a/public/images/pokemon/variant/978-curly_3.png b/public/images/pokemon/variant/978-curly_3.png index 7bc73280dc3..5e811b4ae7d 100644 Binary files a/public/images/pokemon/variant/978-curly_3.png and b/public/images/pokemon/variant/978-curly_3.png differ diff --git a/public/images/pokemon/variant/978-droopy_2.png b/public/images/pokemon/variant/978-droopy_2.png index 4241c9009cc..cd7759606a4 100644 Binary files a/public/images/pokemon/variant/978-droopy_2.png and b/public/images/pokemon/variant/978-droopy_2.png differ diff --git a/public/images/pokemon/variant/978-droopy_3.png b/public/images/pokemon/variant/978-droopy_3.png index b7f2429b04f..d9cf0f0800d 100644 Binary files a/public/images/pokemon/variant/978-droopy_3.png and b/public/images/pokemon/variant/978-droopy_3.png differ diff --git a/public/images/pokemon/variant/978-stretchy_2.png b/public/images/pokemon/variant/978-stretchy_2.png index 786dc3ec254..07f9b62e2e7 100644 Binary files a/public/images/pokemon/variant/978-stretchy_2.png and b/public/images/pokemon/variant/978-stretchy_2.png differ diff --git a/public/images/pokemon/variant/978-stretchy_3.png b/public/images/pokemon/variant/978-stretchy_3.png index 428aa8c9c91..d1686831e1d 100644 Binary files a/public/images/pokemon/variant/978-stretchy_3.png and b/public/images/pokemon/variant/978-stretchy_3.png differ diff --git a/public/images/pokemon/variant/979_1.png b/public/images/pokemon/variant/979_1.png index 01216516c57..352783875e4 100644 Binary files a/public/images/pokemon/variant/979_1.png and b/public/images/pokemon/variant/979_1.png differ diff --git a/public/images/pokemon/variant/979_2.png b/public/images/pokemon/variant/979_2.png index 52828c62f1e..b7111102ed9 100644 Binary files a/public/images/pokemon/variant/979_2.png and b/public/images/pokemon/variant/979_2.png differ diff --git a/public/images/pokemon/variant/979_3.png b/public/images/pokemon/variant/979_3.png index ed4407c2a38..2b187754cea 100644 Binary files a/public/images/pokemon/variant/979_3.png and b/public/images/pokemon/variant/979_3.png differ diff --git a/public/images/pokemon/variant/9_2.png b/public/images/pokemon/variant/9_2.png index 216d1423374..a31ea1e073e 100644 Binary files a/public/images/pokemon/variant/9_2.png and b/public/images/pokemon/variant/9_2.png differ diff --git a/public/images/pokemon/variant/9_3.png b/public/images/pokemon/variant/9_3.png index 0f1bce73356..5ef7a8d739e 100644 Binary files a/public/images/pokemon/variant/9_3.png and b/public/images/pokemon/variant/9_3.png differ diff --git a/public/images/pokemon/variant/back/1022_2.png b/public/images/pokemon/variant/back/1022_2.png index 4a59f687da2..cdd94da0af6 100644 Binary files a/public/images/pokemon/variant/back/1022_2.png and b/public/images/pokemon/variant/back/1022_2.png differ diff --git a/public/images/pokemon/variant/back/1022_3.png b/public/images/pokemon/variant/back/1022_3.png index 846ecdd4f73..2d5df98da9d 100644 Binary files a/public/images/pokemon/variant/back/1022_3.png and b/public/images/pokemon/variant/back/1022_3.png differ diff --git a/public/images/pokemon/variant/back/125_3.png b/public/images/pokemon/variant/back/125_3.png index eef852c92e7..68e9503d1cb 100644 Binary files a/public/images/pokemon/variant/back/125_3.png and b/public/images/pokemon/variant/back/125_3.png differ diff --git a/public/images/pokemon/variant/back/126_2.png b/public/images/pokemon/variant/back/126_2.png index 9f22780f1d8..016cb367db3 100644 Binary files a/public/images/pokemon/variant/back/126_2.png and b/public/images/pokemon/variant/back/126_2.png differ diff --git a/public/images/pokemon/variant/back/180_3.png b/public/images/pokemon/variant/back/180_3.png index 82d8d9c0c68..0cf0357f3c0 100644 Binary files a/public/images/pokemon/variant/back/180_3.png and b/public/images/pokemon/variant/back/180_3.png differ diff --git a/public/images/pokemon/variant/back/181-mega_3.png b/public/images/pokemon/variant/back/181-mega_3.png index 5d30a156cbc..17832724081 100644 Binary files a/public/images/pokemon/variant/back/181-mega_3.png and b/public/images/pokemon/variant/back/181-mega_3.png differ diff --git a/public/images/pokemon/variant/back/199_1.png b/public/images/pokemon/variant/back/199_1.png index 004346df151..66d6fa0a4d7 100644 Binary files a/public/images/pokemon/variant/back/199_1.png and b/public/images/pokemon/variant/back/199_1.png differ diff --git a/public/images/pokemon/variant/back/200_2.png b/public/images/pokemon/variant/back/200_2.png index 0702c2596e1..ef296028bb0 100644 Binary files a/public/images/pokemon/variant/back/200_2.png and b/public/images/pokemon/variant/back/200_2.png differ diff --git a/public/images/pokemon/variant/back/200_3.png b/public/images/pokemon/variant/back/200_3.png index 0f7b4a5fd05..f707e04ea86 100644 Binary files a/public/images/pokemon/variant/back/200_3.png and b/public/images/pokemon/variant/back/200_3.png differ diff --git a/public/images/pokemon/variant/back/212-mega_2.png b/public/images/pokemon/variant/back/212-mega_2.png index adf745f6851..d066510353d 100644 Binary files a/public/images/pokemon/variant/back/212-mega_2.png and b/public/images/pokemon/variant/back/212-mega_2.png differ diff --git a/public/images/pokemon/variant/back/212-mega_3.png b/public/images/pokemon/variant/back/212-mega_3.png index 0cfead081c4..f3c4e5bd110 100644 Binary files a/public/images/pokemon/variant/back/212-mega_3.png and b/public/images/pokemon/variant/back/212-mega_3.png differ diff --git a/public/images/pokemon/variant/back/212_2.png b/public/images/pokemon/variant/back/212_2.png index 9f325d62aa9..7a24be11f16 100644 Binary files a/public/images/pokemon/variant/back/212_2.png and b/public/images/pokemon/variant/back/212_2.png differ diff --git a/public/images/pokemon/variant/back/212_3.png b/public/images/pokemon/variant/back/212_3.png index d06a89140c7..a22f1a9d38e 100644 Binary files a/public/images/pokemon/variant/back/212_3.png and b/public/images/pokemon/variant/back/212_3.png differ diff --git a/public/images/pokemon/variant/back/239_3.png b/public/images/pokemon/variant/back/239_3.png index 02a680ff5a7..b5ce88685a7 100644 Binary files a/public/images/pokemon/variant/back/239_3.png and b/public/images/pokemon/variant/back/239_3.png differ diff --git a/public/images/pokemon/variant/back/244_2.png b/public/images/pokemon/variant/back/244_2.png index 6244b6eb74c..968b5c325bd 100644 Binary files a/public/images/pokemon/variant/back/244_2.png and b/public/images/pokemon/variant/back/244_2.png differ diff --git a/public/images/pokemon/variant/back/244_3.png b/public/images/pokemon/variant/back/244_3.png index 7e04e3d6086..323b9a6b337 100644 Binary files a/public/images/pokemon/variant/back/244_3.png and b/public/images/pokemon/variant/back/244_3.png differ diff --git a/public/images/pokemon/variant/back/291_1.png b/public/images/pokemon/variant/back/291_1.png index dad69ed24af..a465af75920 100644 Binary files a/public/images/pokemon/variant/back/291_1.png and b/public/images/pokemon/variant/back/291_1.png differ diff --git a/public/images/pokemon/variant/back/291_2.png b/public/images/pokemon/variant/back/291_2.png index d3005597f60..6eafa03d9d5 100644 Binary files a/public/images/pokemon/variant/back/291_2.png and b/public/images/pokemon/variant/back/291_2.png differ diff --git a/public/images/pokemon/variant/back/291_3.png b/public/images/pokemon/variant/back/291_3.png index 9c62398a5fc..98c0d6c6b63 100644 Binary files a/public/images/pokemon/variant/back/291_3.png and b/public/images/pokemon/variant/back/291_3.png differ diff --git a/public/images/pokemon/variant/back/292_1.png b/public/images/pokemon/variant/back/292_1.png index ae99178d90a..4625db861b3 100644 Binary files a/public/images/pokemon/variant/back/292_1.png and b/public/images/pokemon/variant/back/292_1.png differ diff --git a/public/images/pokemon/variant/back/292_2.png b/public/images/pokemon/variant/back/292_2.png index fc3d4090a1d..c773f10b69d 100644 Binary files a/public/images/pokemon/variant/back/292_2.png and b/public/images/pokemon/variant/back/292_2.png differ diff --git a/public/images/pokemon/variant/back/292_3.png b/public/images/pokemon/variant/back/292_3.png index 1ebe95bb1b5..164d583eb43 100644 Binary files a/public/images/pokemon/variant/back/292_3.png and b/public/images/pokemon/variant/back/292_3.png differ diff --git a/public/images/pokemon/variant/back/3-mega_2.png b/public/images/pokemon/variant/back/3-mega_2.png index 52f8ed7b580..7bb201ef984 100644 Binary files a/public/images/pokemon/variant/back/3-mega_2.png and b/public/images/pokemon/variant/back/3-mega_2.png differ diff --git a/public/images/pokemon/variant/back/3-mega_3.png b/public/images/pokemon/variant/back/3-mega_3.png index 9d964688f14..5d7fb8ad798 100644 Binary files a/public/images/pokemon/variant/back/3-mega_3.png and b/public/images/pokemon/variant/back/3-mega_3.png differ diff --git a/public/images/pokemon/variant/back/335_2.png b/public/images/pokemon/variant/back/335_2.png index e21955c3033..5e23f357767 100644 Binary files a/public/images/pokemon/variant/back/335_2.png and b/public/images/pokemon/variant/back/335_2.png differ diff --git a/public/images/pokemon/variant/back/335_3.png b/public/images/pokemon/variant/back/335_3.png index 3ccd19f715f..407413dca23 100644 Binary files a/public/images/pokemon/variant/back/335_3.png and b/public/images/pokemon/variant/back/335_3.png differ diff --git a/public/images/pokemon/variant/back/339_2.png b/public/images/pokemon/variant/back/339_2.png index 77dc5e5e936..ceed75f36f6 100644 Binary files a/public/images/pokemon/variant/back/339_2.png and b/public/images/pokemon/variant/back/339_2.png differ diff --git a/public/images/pokemon/variant/back/340_3.png b/public/images/pokemon/variant/back/340_3.png index 6d0b7ab033a..771b424564b 100644 Binary files a/public/images/pokemon/variant/back/340_3.png and b/public/images/pokemon/variant/back/340_3.png differ diff --git a/public/images/pokemon/variant/back/342_2.png b/public/images/pokemon/variant/back/342_2.png index 6238027c434..d5138c51fcf 100644 Binary files a/public/images/pokemon/variant/back/342_2.png and b/public/images/pokemon/variant/back/342_2.png differ diff --git a/public/images/pokemon/variant/back/342_3.png b/public/images/pokemon/variant/back/342_3.png index d68f7fd40e8..af788f45bfa 100644 Binary files a/public/images/pokemon/variant/back/342_3.png and b/public/images/pokemon/variant/back/342_3.png differ diff --git a/public/images/pokemon/variant/back/351-sunny_3.png b/public/images/pokemon/variant/back/351-sunny_3.png index 66f1536eb84..059b85417e1 100644 Binary files a/public/images/pokemon/variant/back/351-sunny_3.png and b/public/images/pokemon/variant/back/351-sunny_3.png differ diff --git a/public/images/pokemon/variant/back/369_2.png b/public/images/pokemon/variant/back/369_2.png index 8510afc4eb4..62b37b70738 100644 Binary files a/public/images/pokemon/variant/back/369_2.png and b/public/images/pokemon/variant/back/369_2.png differ diff --git a/public/images/pokemon/variant/back/369_3.png b/public/images/pokemon/variant/back/369_3.png index 165b5ad0199..69cedd6571c 100644 Binary files a/public/images/pokemon/variant/back/369_3.png and b/public/images/pokemon/variant/back/369_3.png differ diff --git a/public/images/pokemon/variant/back/36_2.png b/public/images/pokemon/variant/back/36_2.png index 8b7bb25cc59..f30397b482f 100644 Binary files a/public/images/pokemon/variant/back/36_2.png and b/public/images/pokemon/variant/back/36_2.png differ diff --git a/public/images/pokemon/variant/back/370_2.png b/public/images/pokemon/variant/back/370_2.png index 48e7d90585f..32d49cd89a3 100644 Binary files a/public/images/pokemon/variant/back/370_2.png and b/public/images/pokemon/variant/back/370_2.png differ diff --git a/public/images/pokemon/variant/back/370_3.png b/public/images/pokemon/variant/back/370_3.png index f5a0768f26b..f20b119f333 100644 Binary files a/public/images/pokemon/variant/back/370_3.png and b/public/images/pokemon/variant/back/370_3.png differ diff --git a/public/images/pokemon/variant/back/383_2.png b/public/images/pokemon/variant/back/383_2.png index 98ac154832f..9c280b56dd7 100644 Binary files a/public/images/pokemon/variant/back/383_2.png and b/public/images/pokemon/variant/back/383_2.png differ diff --git a/public/images/pokemon/variant/back/383_3.png b/public/images/pokemon/variant/back/383_3.png index f8856a6aa2d..b754d969e4a 100644 Binary files a/public/images/pokemon/variant/back/383_3.png and b/public/images/pokemon/variant/back/383_3.png differ diff --git a/public/images/pokemon/variant/back/387_2.png b/public/images/pokemon/variant/back/387_2.png index d55cb1c5384..00c947db821 100644 Binary files a/public/images/pokemon/variant/back/387_2.png and b/public/images/pokemon/variant/back/387_2.png differ diff --git a/public/images/pokemon/variant/back/399_2.png b/public/images/pokemon/variant/back/399_2.png index 55ab33679cb..b71497f9ccf 100644 Binary files a/public/images/pokemon/variant/back/399_2.png and b/public/images/pokemon/variant/back/399_2.png differ diff --git a/public/images/pokemon/variant/back/4080_1.png b/public/images/pokemon/variant/back/4080_1.png index 7f6ba3be1c4..885cd8b2c54 100644 Binary files a/public/images/pokemon/variant/back/4080_1.png and b/public/images/pokemon/variant/back/4080_1.png differ diff --git a/public/images/pokemon/variant/back/412-sandy_1.png b/public/images/pokemon/variant/back/412-sandy_1.png index 9dcd4798761..a8988e8ab22 100644 Binary files a/public/images/pokemon/variant/back/412-sandy_1.png and b/public/images/pokemon/variant/back/412-sandy_1.png differ diff --git a/public/images/pokemon/variant/back/412-sandy_2.png b/public/images/pokemon/variant/back/412-sandy_2.png index de7c581bb73..730e0896c92 100644 Binary files a/public/images/pokemon/variant/back/412-sandy_2.png and b/public/images/pokemon/variant/back/412-sandy_2.png differ diff --git a/public/images/pokemon/variant/back/412-sandy_3.png b/public/images/pokemon/variant/back/412-sandy_3.png index 46022a40775..bf2f2f0b94a 100644 Binary files a/public/images/pokemon/variant/back/412-sandy_3.png and b/public/images/pokemon/variant/back/412-sandy_3.png differ diff --git a/public/images/pokemon/variant/back/4199_1.png b/public/images/pokemon/variant/back/4199_1.png index daaac5a6448..45b81fb3413 100644 Binary files a/public/images/pokemon/variant/back/4199_1.png and b/public/images/pokemon/variant/back/4199_1.png differ diff --git a/public/images/pokemon/variant/back/441_3.png b/public/images/pokemon/variant/back/441_3.png index 271e40e3423..3cfa821a8f9 100644 Binary files a/public/images/pokemon/variant/back/441_3.png and b/public/images/pokemon/variant/back/441_3.png differ diff --git a/public/images/pokemon/variant/back/457_2.png b/public/images/pokemon/variant/back/457_2.png index 9168013d2e6..f41fb311b01 100644 Binary files a/public/images/pokemon/variant/back/457_2.png and b/public/images/pokemon/variant/back/457_2.png differ diff --git a/public/images/pokemon/variant/back/458_2.png b/public/images/pokemon/variant/back/458_2.png index 5c1db97b56d..d593fd73842 100644 Binary files a/public/images/pokemon/variant/back/458_2.png and b/public/images/pokemon/variant/back/458_2.png differ diff --git a/public/images/pokemon/variant/back/458_3.png b/public/images/pokemon/variant/back/458_3.png index 7cf9d011c04..605d0dac846 100644 Binary files a/public/images/pokemon/variant/back/458_3.png and b/public/images/pokemon/variant/back/458_3.png differ diff --git a/public/images/pokemon/variant/back/466_1.png b/public/images/pokemon/variant/back/466_1.png index 32f481f6885..eab00428a23 100644 Binary files a/public/images/pokemon/variant/back/466_1.png and b/public/images/pokemon/variant/back/466_1.png differ diff --git a/public/images/pokemon/variant/back/469_2.png b/public/images/pokemon/variant/back/469_2.png index 6e064707bba..b83cf418a57 100644 Binary files a/public/images/pokemon/variant/back/469_2.png and b/public/images/pokemon/variant/back/469_2.png differ diff --git a/public/images/pokemon/variant/back/470_1.png b/public/images/pokemon/variant/back/470_1.png index a156101f2cf..7e64d672b38 100644 Binary files a/public/images/pokemon/variant/back/470_1.png and b/public/images/pokemon/variant/back/470_1.png differ diff --git a/public/images/pokemon/variant/back/470_2.png b/public/images/pokemon/variant/back/470_2.png index 69e75827b0a..f062750b14f 100644 Binary files a/public/images/pokemon/variant/back/470_2.png and b/public/images/pokemon/variant/back/470_2.png differ diff --git a/public/images/pokemon/variant/back/472_3.png b/public/images/pokemon/variant/back/472_3.png index 79b07833534..a6e22d85113 100644 Binary files a/public/images/pokemon/variant/back/472_3.png and b/public/images/pokemon/variant/back/472_3.png differ diff --git a/public/images/pokemon/variant/back/478_2.png b/public/images/pokemon/variant/back/478_2.png index 9d165174556..979bfc9fd11 100644 Binary files a/public/images/pokemon/variant/back/478_2.png and b/public/images/pokemon/variant/back/478_2.png differ diff --git a/public/images/pokemon/variant/back/529_2.png b/public/images/pokemon/variant/back/529_2.png index 06e853c19c2..22d3d5958ba 100644 Binary files a/public/images/pokemon/variant/back/529_2.png and b/public/images/pokemon/variant/back/529_2.png differ diff --git a/public/images/pokemon/variant/back/529_3.png b/public/images/pokemon/variant/back/529_3.png index 935c5788841..8458afd7642 100644 Binary files a/public/images/pokemon/variant/back/529_3.png and b/public/images/pokemon/variant/back/529_3.png differ diff --git a/public/images/pokemon/variant/back/530_3.png b/public/images/pokemon/variant/back/530_3.png index d5b7b9b651b..ec2a1e7cd77 100644 Binary files a/public/images/pokemon/variant/back/530_3.png and b/public/images/pokemon/variant/back/530_3.png differ diff --git a/public/images/pokemon/variant/back/539_2.png b/public/images/pokemon/variant/back/539_2.png index e4bca835949..01956ca440c 100644 Binary files a/public/images/pokemon/variant/back/539_2.png and b/public/images/pokemon/variant/back/539_2.png differ diff --git a/public/images/pokemon/variant/back/539_3.png b/public/images/pokemon/variant/back/539_3.png index fb28b78459c..b4126047a5c 100644 Binary files a/public/images/pokemon/variant/back/539_3.png and b/public/images/pokemon/variant/back/539_3.png differ diff --git a/public/images/pokemon/variant/back/585-autumn_1.png b/public/images/pokemon/variant/back/585-autumn_1.png index 48c5968bff7..147e14fc5fa 100644 Binary files a/public/images/pokemon/variant/back/585-autumn_1.png and b/public/images/pokemon/variant/back/585-autumn_1.png differ diff --git a/public/images/pokemon/variant/back/585-spring_1.png b/public/images/pokemon/variant/back/585-spring_1.png index e3d6d7f75bd..e715844e976 100644 Binary files a/public/images/pokemon/variant/back/585-spring_1.png and b/public/images/pokemon/variant/back/585-spring_1.png differ diff --git a/public/images/pokemon/variant/back/585-winter_1.png b/public/images/pokemon/variant/back/585-winter_1.png index fe85cebd87d..24d991327f7 100644 Binary files a/public/images/pokemon/variant/back/585-winter_1.png and b/public/images/pokemon/variant/back/585-winter_1.png differ diff --git a/public/images/pokemon/variant/back/592_3.png b/public/images/pokemon/variant/back/592_3.png index 88ac9e57362..f7469e36ec6 100644 Binary files a/public/images/pokemon/variant/back/592_3.png and b/public/images/pokemon/variant/back/592_3.png differ diff --git a/public/images/pokemon/variant/back/594_3.png b/public/images/pokemon/variant/back/594_3.png index 9e4215d42b7..4f1c28d8335 100644 Binary files a/public/images/pokemon/variant/back/594_3.png and b/public/images/pokemon/variant/back/594_3.png differ diff --git a/public/images/pokemon/variant/back/618_2.png b/public/images/pokemon/variant/back/618_2.png index e74c6db3f04..a0077a1f023 100644 Binary files a/public/images/pokemon/variant/back/618_2.png and b/public/images/pokemon/variant/back/618_2.png differ diff --git a/public/images/pokemon/variant/back/631_2.png b/public/images/pokemon/variant/back/631_2.png index d5d6af70546..b3cee0d8baa 100644 Binary files a/public/images/pokemon/variant/back/631_2.png and b/public/images/pokemon/variant/back/631_2.png differ diff --git a/public/images/pokemon/variant/back/631_3.png b/public/images/pokemon/variant/back/631_3.png index bb029fd7f3c..a052b6ea4b1 100644 Binary files a/public/images/pokemon/variant/back/631_3.png and b/public/images/pokemon/variant/back/631_3.png differ diff --git a/public/images/pokemon/variant/back/666-fancy_2.png b/public/images/pokemon/variant/back/666-fancy_2.png index d51a6403f3b..a1e23edd659 100644 Binary files a/public/images/pokemon/variant/back/666-fancy_2.png and b/public/images/pokemon/variant/back/666-fancy_2.png differ diff --git a/public/images/pokemon/variant/back/666-fancy_3.png b/public/images/pokemon/variant/back/666-fancy_3.png index aeca21cbeb5..6d15bc3f680 100644 Binary files a/public/images/pokemon/variant/back/666-fancy_3.png and b/public/images/pokemon/variant/back/666-fancy_3.png differ diff --git a/public/images/pokemon/variant/back/666-river_2.png b/public/images/pokemon/variant/back/666-river_2.png index 06c1b09d749..8d829e974f4 100644 Binary files a/public/images/pokemon/variant/back/666-river_2.png and b/public/images/pokemon/variant/back/666-river_2.png differ diff --git a/public/images/pokemon/variant/back/696_3.png b/public/images/pokemon/variant/back/696_3.png index 87dc6ff576c..76bced189af 100644 Binary files a/public/images/pokemon/variant/back/696_3.png and b/public/images/pokemon/variant/back/696_3.png differ diff --git a/public/images/pokemon/variant/back/697_3.png b/public/images/pokemon/variant/back/697_3.png index 430bde3ae6d..cea5cfb6830 100644 Binary files a/public/images/pokemon/variant/back/697_3.png and b/public/images/pokemon/variant/back/697_3.png differ diff --git a/public/images/pokemon/variant/back/715_2.png b/public/images/pokemon/variant/back/715_2.png index 91bebbc7e58..d9e04847334 100644 Binary files a/public/images/pokemon/variant/back/715_2.png and b/public/images/pokemon/variant/back/715_2.png differ diff --git a/public/images/pokemon/variant/back/715_3.png b/public/images/pokemon/variant/back/715_3.png index 123e9a7fa1d..11069652972 100644 Binary files a/public/images/pokemon/variant/back/715_3.png and b/public/images/pokemon/variant/back/715_3.png differ diff --git a/public/images/pokemon/variant/back/742_2.png b/public/images/pokemon/variant/back/742_2.png index 955cd3514ae..cfcc6c31f20 100644 Binary files a/public/images/pokemon/variant/back/742_2.png and b/public/images/pokemon/variant/back/742_2.png differ diff --git a/public/images/pokemon/variant/back/742_3.png b/public/images/pokemon/variant/back/742_3.png index a42604a6b56..a8157351738 100644 Binary files a/public/images/pokemon/variant/back/742_3.png and b/public/images/pokemon/variant/back/742_3.png differ diff --git a/public/images/pokemon/variant/back/743_2.png b/public/images/pokemon/variant/back/743_2.png index 983addac875..3e90381dcc1 100644 Binary files a/public/images/pokemon/variant/back/743_2.png and b/public/images/pokemon/variant/back/743_2.png differ diff --git a/public/images/pokemon/variant/back/743_3.png b/public/images/pokemon/variant/back/743_3.png index b892f735a5b..03d5b2719ff 100644 Binary files a/public/images/pokemon/variant/back/743_3.png and b/public/images/pokemon/variant/back/743_3.png differ diff --git a/public/images/pokemon/variant/back/754_2.png b/public/images/pokemon/variant/back/754_2.png index 234f96b9f45..af52b9c5f44 100644 Binary files a/public/images/pokemon/variant/back/754_2.png and b/public/images/pokemon/variant/back/754_2.png differ diff --git a/public/images/pokemon/variant/back/754_3.png b/public/images/pokemon/variant/back/754_3.png index 7a99986e40e..e00a2353a60 100644 Binary files a/public/images/pokemon/variant/back/754_3.png and b/public/images/pokemon/variant/back/754_3.png differ diff --git a/public/images/pokemon/variant/back/791_1.png b/public/images/pokemon/variant/back/791_1.png index 9d5da99ce5c..647884de128 100644 Binary files a/public/images/pokemon/variant/back/791_1.png and b/public/images/pokemon/variant/back/791_1.png differ diff --git a/public/images/pokemon/variant/back/7_2.png b/public/images/pokemon/variant/back/7_2.png index 7f0970f95bc..1deb4d30f61 100644 Binary files a/public/images/pokemon/variant/back/7_2.png and b/public/images/pokemon/variant/back/7_2.png differ diff --git a/public/images/pokemon/variant/back/7_3.png b/public/images/pokemon/variant/back/7_3.png index 2758617eea9..8111d99023e 100644 Binary files a/public/images/pokemon/variant/back/7_3.png and b/public/images/pokemon/variant/back/7_3.png differ diff --git a/public/images/pokemon/variant/back/823-gigantamax_2.png b/public/images/pokemon/variant/back/823-gigantamax_2.png index 7ccc99014a6..d37529893d9 100644 Binary files a/public/images/pokemon/variant/back/823-gigantamax_2.png and b/public/images/pokemon/variant/back/823-gigantamax_2.png differ diff --git a/public/images/pokemon/variant/back/823-gigantamax_3.png b/public/images/pokemon/variant/back/823-gigantamax_3.png index a85e9813268..9b329a4d6f6 100644 Binary files a/public/images/pokemon/variant/back/823-gigantamax_3.png and b/public/images/pokemon/variant/back/823-gigantamax_3.png differ diff --git a/public/images/pokemon/variant/back/83_2.png b/public/images/pokemon/variant/back/83_2.png index 3dda813cf92..103155eb387 100644 Binary files a/public/images/pokemon/variant/back/83_2.png and b/public/images/pokemon/variant/back/83_2.png differ diff --git a/public/images/pokemon/variant/back/83_3.png b/public/images/pokemon/variant/back/83_3.png index b8de9d82cc7..e06e48e577f 100644 Binary files a/public/images/pokemon/variant/back/83_3.png and b/public/images/pokemon/variant/back/83_3.png differ diff --git a/public/images/pokemon/variant/back/857_2.png b/public/images/pokemon/variant/back/857_2.png index bbc09762342..2481e0b825f 100644 Binary files a/public/images/pokemon/variant/back/857_2.png and b/public/images/pokemon/variant/back/857_2.png differ diff --git a/public/images/pokemon/variant/back/857_3.png b/public/images/pokemon/variant/back/857_3.png index 9f77ff938c6..0b14194a26f 100644 Binary files a/public/images/pokemon/variant/back/857_3.png and b/public/images/pokemon/variant/back/857_3.png differ diff --git a/public/images/pokemon/variant/back/862_2.png b/public/images/pokemon/variant/back/862_2.png index 705898eb9a7..1a79d39d63f 100644 Binary files a/public/images/pokemon/variant/back/862_2.png and b/public/images/pokemon/variant/back/862_2.png differ diff --git a/public/images/pokemon/variant/back/862_3.png b/public/images/pokemon/variant/back/862_3.png index 92f81bfe220..d8e6d2b5639 100644 Binary files a/public/images/pokemon/variant/back/862_3.png and b/public/images/pokemon/variant/back/862_3.png differ diff --git a/public/images/pokemon/variant/back/881_2.png b/public/images/pokemon/variant/back/881_2.png index 76176d7d5fe..2060481d855 100644 Binary files a/public/images/pokemon/variant/back/881_2.png and b/public/images/pokemon/variant/back/881_2.png differ diff --git a/public/images/pokemon/variant/back/881_3.png b/public/images/pokemon/variant/back/881_3.png index c255e83cc16..6ed3c2495ba 100644 Binary files a/public/images/pokemon/variant/back/881_3.png and b/public/images/pokemon/variant/back/881_3.png differ diff --git a/public/images/pokemon/variant/back/8_2.png b/public/images/pokemon/variant/back/8_2.png index d0171dd1bdc..8c3605eb75b 100644 Binary files a/public/images/pokemon/variant/back/8_2.png and b/public/images/pokemon/variant/back/8_2.png differ diff --git a/public/images/pokemon/variant/back/8_3.png b/public/images/pokemon/variant/back/8_3.png index ea7724838db..c74f82777ce 100644 Binary files a/public/images/pokemon/variant/back/8_3.png and b/public/images/pokemon/variant/back/8_3.png differ diff --git a/public/images/pokemon/variant/back/9-gigantamax_3.png b/public/images/pokemon/variant/back/9-gigantamax_3.png index c98249e14d5..444264fa4fd 100644 Binary files a/public/images/pokemon/variant/back/9-gigantamax_3.png and b/public/images/pokemon/variant/back/9-gigantamax_3.png differ diff --git a/public/images/pokemon/variant/back/9-mega_2.png b/public/images/pokemon/variant/back/9-mega_2.png index e961ace2e5c..02987564bea 100644 Binary files a/public/images/pokemon/variant/back/9-mega_2.png and b/public/images/pokemon/variant/back/9-mega_2.png differ diff --git a/public/images/pokemon/variant/back/9-mega_3.png b/public/images/pokemon/variant/back/9-mega_3.png index 95a7babe58b..00150d4dd46 100644 Binary files a/public/images/pokemon/variant/back/9-mega_3.png and b/public/images/pokemon/variant/back/9-mega_3.png differ diff --git a/public/images/pokemon/variant/back/910_2.png b/public/images/pokemon/variant/back/910_2.png index 4efa5be2565..8c4dd643574 100644 Binary files a/public/images/pokemon/variant/back/910_2.png and b/public/images/pokemon/variant/back/910_2.png differ diff --git a/public/images/pokemon/variant/back/910_3.png b/public/images/pokemon/variant/back/910_3.png index c5f4b605dcb..ad622de11a4 100644 Binary files a/public/images/pokemon/variant/back/910_3.png and b/public/images/pokemon/variant/back/910_3.png differ diff --git a/public/images/pokemon/variant/back/92_1.png b/public/images/pokemon/variant/back/92_1.png index 20faf4213ca..96a70ecbf7b 100644 Binary files a/public/images/pokemon/variant/back/92_1.png and b/public/images/pokemon/variant/back/92_1.png differ diff --git a/public/images/pokemon/variant/back/92_2.png b/public/images/pokemon/variant/back/92_2.png index 6a7e13cc25e..711ece9d98b 100644 Binary files a/public/images/pokemon/variant/back/92_2.png and b/public/images/pokemon/variant/back/92_2.png differ diff --git a/public/images/pokemon/variant/back/92_3.png b/public/images/pokemon/variant/back/92_3.png index df995a87d55..918e0ee35f8 100644 Binary files a/public/images/pokemon/variant/back/92_3.png and b/public/images/pokemon/variant/back/92_3.png differ diff --git a/public/images/pokemon/variant/back/970_2.png b/public/images/pokemon/variant/back/970_2.png index 2e07eb2fb07..4f3a7eb76ef 100644 Binary files a/public/images/pokemon/variant/back/970_2.png and b/public/images/pokemon/variant/back/970_2.png differ diff --git a/public/images/pokemon/variant/back/978-curly_2.png b/public/images/pokemon/variant/back/978-curly_2.png index 4a5ede3e81a..70bde1acb2f 100644 Binary files a/public/images/pokemon/variant/back/978-curly_2.png and b/public/images/pokemon/variant/back/978-curly_2.png differ diff --git a/public/images/pokemon/variant/back/978-curly_3.png b/public/images/pokemon/variant/back/978-curly_3.png index af6d8a8aa33..877dbbaee6f 100644 Binary files a/public/images/pokemon/variant/back/978-curly_3.png and b/public/images/pokemon/variant/back/978-curly_3.png differ diff --git a/public/images/pokemon/variant/back/978-droopy_2.png b/public/images/pokemon/variant/back/978-droopy_2.png index 23e1b955d4d..7259f4e5635 100644 Binary files a/public/images/pokemon/variant/back/978-droopy_2.png and b/public/images/pokemon/variant/back/978-droopy_2.png differ diff --git a/public/images/pokemon/variant/back/978-droopy_3.png b/public/images/pokemon/variant/back/978-droopy_3.png index 6255349f590..9ba70e6d395 100644 Binary files a/public/images/pokemon/variant/back/978-droopy_3.png and b/public/images/pokemon/variant/back/978-droopy_3.png differ diff --git a/public/images/pokemon/variant/back/982-three-segment_3.png b/public/images/pokemon/variant/back/982-three-segment_3.png index 3286d3331a5..ed5d835ab16 100644 Binary files a/public/images/pokemon/variant/back/982-three-segment_3.png and b/public/images/pokemon/variant/back/982-three-segment_3.png differ diff --git a/public/images/pokemon/variant/back/982_3.png b/public/images/pokemon/variant/back/982_3.png index b6b0ef0c2f4..0c8766b59cb 100644 Binary files a/public/images/pokemon/variant/back/982_3.png and b/public/images/pokemon/variant/back/982_3.png differ diff --git a/public/images/pokemon/variant/back/9_2.png b/public/images/pokemon/variant/back/9_2.png index a55c0c139fb..409472b2e52 100644 Binary files a/public/images/pokemon/variant/back/9_2.png and b/public/images/pokemon/variant/back/9_2.png differ diff --git a/public/images/pokemon/variant/back/9_3.png b/public/images/pokemon/variant/back/9_3.png index edae3f66b71..2cb0b1a3917 100644 Binary files a/public/images/pokemon/variant/back/9_3.png and b/public/images/pokemon/variant/back/9_3.png differ diff --git a/public/images/pokemon/variant/back/female/399_2.png b/public/images/pokemon/variant/back/female/399_2.png index fcd948a0ef9..b71497f9ccf 100644 Binary files a/public/images/pokemon/variant/back/female/399_2.png and b/public/images/pokemon/variant/back/female/399_2.png differ diff --git a/public/images/pokemon/variant/back/female/418_2.png b/public/images/pokemon/variant/back/female/418_2.png index fc6ad0362df..03a1b2b7dd4 100644 Binary files a/public/images/pokemon/variant/back/female/418_2.png and b/public/images/pokemon/variant/back/female/418_2.png differ diff --git a/public/images/pokemon/variant/back/female/418_3.png b/public/images/pokemon/variant/back/female/418_3.png index a9051456db9..faf166b7184 100644 Binary files a/public/images/pokemon/variant/back/female/418_3.png and b/public/images/pokemon/variant/back/female/418_3.png differ diff --git a/public/images/pokemon/variant/back/female/41_2.png b/public/images/pokemon/variant/back/female/41_2.png index bd1e22238cc..4fdb671c61a 100644 Binary files a/public/images/pokemon/variant/back/female/41_2.png and b/public/images/pokemon/variant/back/female/41_2.png differ diff --git a/public/images/pokemon/variant/back/female/41_3.png b/public/images/pokemon/variant/back/female/41_3.png index fec4f49c599..f494bd5b07e 100644 Binary files a/public/images/pokemon/variant/back/female/41_3.png and b/public/images/pokemon/variant/back/female/41_3.png differ diff --git a/public/images/pokemon/variant/back/female/42_2.png b/public/images/pokemon/variant/back/female/42_2.png index 61360275ca1..2fa196d973a 100644 Binary files a/public/images/pokemon/variant/back/female/42_2.png and b/public/images/pokemon/variant/back/female/42_2.png differ diff --git a/public/images/pokemon/variant/back/female/42_3.png b/public/images/pokemon/variant/back/female/42_3.png index bd86c83f3c0..4e88627d6b6 100644 Binary files a/public/images/pokemon/variant/back/female/42_3.png and b/public/images/pokemon/variant/back/female/42_3.png differ diff --git a/public/images/pokemon/variant/exp/181-mega_3.png b/public/images/pokemon/variant/exp/181-mega_3.png index 2e88beea872..7172bc45244 100644 Binary files a/public/images/pokemon/variant/exp/181-mega_3.png and b/public/images/pokemon/variant/exp/181-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/212-mega_2.png b/public/images/pokemon/variant/exp/212-mega_2.png index d08116555cd..2665dfbc253 100644 Binary files a/public/images/pokemon/variant/exp/212-mega_2.png and b/public/images/pokemon/variant/exp/212-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/212-mega_3.png b/public/images/pokemon/variant/exp/212-mega_3.png index c93db623fda..75226e68010 100644 Binary files a/public/images/pokemon/variant/exp/212-mega_3.png and b/public/images/pokemon/variant/exp/212-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/3-mega_2.png b/public/images/pokemon/variant/exp/3-mega_2.png index 97dce2ae673..b9cb20aba0a 100644 Binary files a/public/images/pokemon/variant/exp/3-mega_2.png and b/public/images/pokemon/variant/exp/3-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/3-mega_3.png b/public/images/pokemon/variant/exp/3-mega_3.png index 277b3c82082..ddd1e998130 100644 Binary files a/public/images/pokemon/variant/exp/3-mega_3.png and b/public/images/pokemon/variant/exp/3-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/334-mega_2.png b/public/images/pokemon/variant/exp/334-mega_2.png index 7a6fdb20cf2..9588df214d0 100644 Binary files a/public/images/pokemon/variant/exp/334-mega_2.png and b/public/images/pokemon/variant/exp/334-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/384-mega_2.png b/public/images/pokemon/variant/exp/384-mega_2.png index dc03a7ba8e2..57ed787e8da 100644 Binary files a/public/images/pokemon/variant/exp/384-mega_2.png and b/public/images/pokemon/variant/exp/384-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/4080_1.png b/public/images/pokemon/variant/exp/4080_1.png index a7e6de6cff7..aad11f1dd4a 100644 Binary files a/public/images/pokemon/variant/exp/4080_1.png and b/public/images/pokemon/variant/exp/4080_1.png differ diff --git a/public/images/pokemon/variant/exp/4199_1.png b/public/images/pokemon/variant/exp/4199_1.png index 4ad5b00b4c7..0a22d90da05 100644 Binary files a/public/images/pokemon/variant/exp/4199_1.png and b/public/images/pokemon/variant/exp/4199_1.png differ diff --git a/public/images/pokemon/variant/exp/666-fancy_2.png b/public/images/pokemon/variant/exp/666-fancy_2.png index df1343095d0..68af4d0a07a 100644 Binary files a/public/images/pokemon/variant/exp/666-fancy_2.png and b/public/images/pokemon/variant/exp/666-fancy_2.png differ diff --git a/public/images/pokemon/variant/exp/666-fancy_3.png b/public/images/pokemon/variant/exp/666-fancy_3.png index 4a48c5cc236..a8e39976f2a 100644 Binary files a/public/images/pokemon/variant/exp/666-fancy_3.png and b/public/images/pokemon/variant/exp/666-fancy_3.png differ diff --git a/public/images/pokemon/variant/exp/666-meadow_2.png b/public/images/pokemon/variant/exp/666-meadow_2.png index 5ada6bb51be..8aada72636f 100644 Binary files a/public/images/pokemon/variant/exp/666-meadow_2.png and b/public/images/pokemon/variant/exp/666-meadow_2.png differ diff --git a/public/images/pokemon/variant/exp/666-meadow_3.png b/public/images/pokemon/variant/exp/666-meadow_3.png index 2acb4732409..146ea5e2ba9 100644 Binary files a/public/images/pokemon/variant/exp/666-meadow_3.png and b/public/images/pokemon/variant/exp/666-meadow_3.png differ diff --git a/public/images/pokemon/variant/exp/666-poke-ball_3.png b/public/images/pokemon/variant/exp/666-poke-ball_3.png index 9e821efd1eb..767a55ab849 100644 Binary files a/public/images/pokemon/variant/exp/666-poke-ball_3.png and b/public/images/pokemon/variant/exp/666-poke-ball_3.png differ diff --git a/public/images/pokemon/variant/exp/666-river_2.png b/public/images/pokemon/variant/exp/666-river_2.png index 7dc96690328..863acbbdc76 100644 Binary files a/public/images/pokemon/variant/exp/666-river_2.png and b/public/images/pokemon/variant/exp/666-river_2.png differ diff --git a/public/images/pokemon/variant/exp/696_2.png b/public/images/pokemon/variant/exp/696_2.png index 031589b4c30..cfc40b59d39 100644 Binary files a/public/images/pokemon/variant/exp/696_2.png and b/public/images/pokemon/variant/exp/696_2.png differ diff --git a/public/images/pokemon/variant/exp/696_3.png b/public/images/pokemon/variant/exp/696_3.png index 8ec6baa4724..647aba679f6 100644 Binary files a/public/images/pokemon/variant/exp/696_3.png and b/public/images/pokemon/variant/exp/696_3.png differ diff --git a/public/images/pokemon/variant/exp/697_3.png b/public/images/pokemon/variant/exp/697_3.png index f58c494a094..b18579c9494 100644 Binary files a/public/images/pokemon/variant/exp/697_3.png and b/public/images/pokemon/variant/exp/697_3.png differ diff --git a/public/images/pokemon/variant/exp/715_2.png b/public/images/pokemon/variant/exp/715_2.png index 4383b8917ae..22fb386440a 100644 Binary files a/public/images/pokemon/variant/exp/715_2.png and b/public/images/pokemon/variant/exp/715_2.png differ diff --git a/public/images/pokemon/variant/exp/717_2.png b/public/images/pokemon/variant/exp/717_2.png index 30b5a9863de..3e11dfa8837 100644 Binary files a/public/images/pokemon/variant/exp/717_2.png and b/public/images/pokemon/variant/exp/717_2.png differ diff --git a/public/images/pokemon/variant/exp/717_3.png b/public/images/pokemon/variant/exp/717_3.png index ad17cdd305f..e39ddfc55d0 100644 Binary files a/public/images/pokemon/variant/exp/717_3.png and b/public/images/pokemon/variant/exp/717_3.png differ diff --git a/public/images/pokemon/variant/exp/729_2.png b/public/images/pokemon/variant/exp/729_2.png index c5c309dbaee..8349e63c91a 100644 Binary files a/public/images/pokemon/variant/exp/729_2.png and b/public/images/pokemon/variant/exp/729_2.png differ diff --git a/public/images/pokemon/variant/exp/729_3.png b/public/images/pokemon/variant/exp/729_3.png index 75a11c74cdc..a4b4197f18a 100644 Binary files a/public/images/pokemon/variant/exp/729_3.png and b/public/images/pokemon/variant/exp/729_3.png differ diff --git a/public/images/pokemon/variant/exp/730_2.png b/public/images/pokemon/variant/exp/730_2.png index f2ef5220ea9..f7a1b20a9be 100644 Binary files a/public/images/pokemon/variant/exp/730_2.png and b/public/images/pokemon/variant/exp/730_2.png differ diff --git a/public/images/pokemon/variant/exp/742_2.png b/public/images/pokemon/variant/exp/742_2.png index a2a238353ea..d4495c7b805 100644 Binary files a/public/images/pokemon/variant/exp/742_2.png and b/public/images/pokemon/variant/exp/742_2.png differ diff --git a/public/images/pokemon/variant/exp/742_3.png b/public/images/pokemon/variant/exp/742_3.png index a4c4f372972..8d55c3263b0 100644 Binary files a/public/images/pokemon/variant/exp/742_3.png and b/public/images/pokemon/variant/exp/742_3.png differ diff --git a/public/images/pokemon/variant/exp/743_2.png b/public/images/pokemon/variant/exp/743_2.png index dae1418901d..6236408f2ca 100644 Binary files a/public/images/pokemon/variant/exp/743_2.png and b/public/images/pokemon/variant/exp/743_2.png differ diff --git a/public/images/pokemon/variant/exp/743_3.png b/public/images/pokemon/variant/exp/743_3.png index 99d6ae3763e..2759946a0fd 100644 Binary files a/public/images/pokemon/variant/exp/743_3.png and b/public/images/pokemon/variant/exp/743_3.png differ diff --git a/public/images/pokemon/variant/exp/747_2.png b/public/images/pokemon/variant/exp/747_2.png index cf3832c9236..5afb5dbe45e 100644 Binary files a/public/images/pokemon/variant/exp/747_2.png and b/public/images/pokemon/variant/exp/747_2.png differ diff --git a/public/images/pokemon/variant/exp/747_3.png b/public/images/pokemon/variant/exp/747_3.png index 9fd3b92aa3e..ceb750efe0c 100644 Binary files a/public/images/pokemon/variant/exp/747_3.png and b/public/images/pokemon/variant/exp/747_3.png differ diff --git a/public/images/pokemon/variant/exp/754_2.png b/public/images/pokemon/variant/exp/754_2.png index fc7e9eadac6..c1c55966656 100644 Binary files a/public/images/pokemon/variant/exp/754_2.png and b/public/images/pokemon/variant/exp/754_2.png differ diff --git a/public/images/pokemon/variant/exp/754_3.png b/public/images/pokemon/variant/exp/754_3.png index c9ad7c5d4ca..dca99a4eb83 100644 Binary files a/public/images/pokemon/variant/exp/754_3.png and b/public/images/pokemon/variant/exp/754_3.png differ diff --git a/public/images/pokemon/variant/exp/771_2.png b/public/images/pokemon/variant/exp/771_2.png index ec47015842d..f2059e6a8eb 100644 Binary files a/public/images/pokemon/variant/exp/771_2.png and b/public/images/pokemon/variant/exp/771_2.png differ diff --git a/public/images/pokemon/variant/exp/771_3.png b/public/images/pokemon/variant/exp/771_3.png index 4a0ac89ac2e..9016079d1cb 100644 Binary files a/public/images/pokemon/variant/exp/771_3.png and b/public/images/pokemon/variant/exp/771_3.png differ diff --git a/public/images/pokemon/variant/exp/791_1.png b/public/images/pokemon/variant/exp/791_1.png index 6c337194ec3..4d5f210ec9f 100644 Binary files a/public/images/pokemon/variant/exp/791_1.png and b/public/images/pokemon/variant/exp/791_1.png differ diff --git a/public/images/pokemon/variant/exp/793_2.png b/public/images/pokemon/variant/exp/793_2.png index 9c70c5a94ee..13f22cffdda 100644 Binary files a/public/images/pokemon/variant/exp/793_2.png and b/public/images/pokemon/variant/exp/793_2.png differ diff --git a/public/images/pokemon/variant/exp/793_3.png b/public/images/pokemon/variant/exp/793_3.png index a758fcb28a4..137cdd97d6d 100644 Binary files a/public/images/pokemon/variant/exp/793_3.png and b/public/images/pokemon/variant/exp/793_3.png differ diff --git a/public/images/pokemon/variant/exp/821_2.png b/public/images/pokemon/variant/exp/821_2.png index 5de24ec92a2..84bb53c19ff 100644 Binary files a/public/images/pokemon/variant/exp/821_2.png and b/public/images/pokemon/variant/exp/821_2.png differ diff --git a/public/images/pokemon/variant/exp/821_3.png b/public/images/pokemon/variant/exp/821_3.png index 7c2bd32a288..eab0bca889f 100644 Binary files a/public/images/pokemon/variant/exp/821_3.png and b/public/images/pokemon/variant/exp/821_3.png differ diff --git a/public/images/pokemon/variant/exp/836_2.png b/public/images/pokemon/variant/exp/836_2.png index 9729f1dd4cf..2961e1015a9 100644 Binary files a/public/images/pokemon/variant/exp/836_2.png and b/public/images/pokemon/variant/exp/836_2.png differ diff --git a/public/images/pokemon/variant/exp/836_3.png b/public/images/pokemon/variant/exp/836_3.png index 76caf187b4d..a48d218ed98 100644 Binary files a/public/images/pokemon/variant/exp/836_3.png and b/public/images/pokemon/variant/exp/836_3.png differ diff --git a/public/images/pokemon/variant/exp/857_2.png b/public/images/pokemon/variant/exp/857_2.png index 2a60b21cef0..c98f602a8e5 100644 Binary files a/public/images/pokemon/variant/exp/857_2.png and b/public/images/pokemon/variant/exp/857_2.png differ diff --git a/public/images/pokemon/variant/exp/857_3.png b/public/images/pokemon/variant/exp/857_3.png index f9e25f22cc4..1b5f73c35de 100644 Binary files a/public/images/pokemon/variant/exp/857_3.png and b/public/images/pokemon/variant/exp/857_3.png differ diff --git a/public/images/pokemon/variant/exp/862_2.png b/public/images/pokemon/variant/exp/862_2.png index 1ed022bfdf7..d97613e5e89 100644 Binary files a/public/images/pokemon/variant/exp/862_2.png and b/public/images/pokemon/variant/exp/862_2.png differ diff --git a/public/images/pokemon/variant/exp/862_3.png b/public/images/pokemon/variant/exp/862_3.png index 79fb464d72f..9f6857d961a 100644 Binary files a/public/images/pokemon/variant/exp/862_3.png and b/public/images/pokemon/variant/exp/862_3.png differ diff --git a/public/images/pokemon/variant/exp/882_2.png b/public/images/pokemon/variant/exp/882_2.png index a702a507f22..fa0c3825cc1 100644 Binary files a/public/images/pokemon/variant/exp/882_2.png and b/public/images/pokemon/variant/exp/882_2.png differ diff --git a/public/images/pokemon/variant/exp/890-eternamax_2.png b/public/images/pokemon/variant/exp/890-eternamax_2.png index b234ec88e16..2327900b971 100644 Binary files a/public/images/pokemon/variant/exp/890-eternamax_2.png and b/public/images/pokemon/variant/exp/890-eternamax_2.png differ diff --git a/public/images/pokemon/variant/exp/890-eternamax_3.png b/public/images/pokemon/variant/exp/890-eternamax_3.png index f24e74283c5..140837cfbd0 100644 Binary files a/public/images/pokemon/variant/exp/890-eternamax_3.png and b/public/images/pokemon/variant/exp/890-eternamax_3.png differ diff --git a/public/images/pokemon/variant/exp/890_2.png b/public/images/pokemon/variant/exp/890_2.png index 036ee01f676..2412e5f95d9 100644 Binary files a/public/images/pokemon/variant/exp/890_2.png and b/public/images/pokemon/variant/exp/890_2.png differ diff --git a/public/images/pokemon/variant/exp/890_3.png b/public/images/pokemon/variant/exp/890_3.png index fb757ecc5cd..855b646e514 100644 Binary files a/public/images/pokemon/variant/exp/890_3.png and b/public/images/pokemon/variant/exp/890_3.png differ diff --git a/public/images/pokemon/variant/exp/9-mega_2.png b/public/images/pokemon/variant/exp/9-mega_2.png index c3c06c1eca7..0a56f1a3a68 100644 Binary files a/public/images/pokemon/variant/exp/9-mega_2.png and b/public/images/pokemon/variant/exp/9-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/9-mega_3.png b/public/images/pokemon/variant/exp/9-mega_3.png index d7a4b6f9140..606f18d6337 100644 Binary files a/public/images/pokemon/variant/exp/9-mega_3.png and b/public/images/pokemon/variant/exp/9-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/910_2.png b/public/images/pokemon/variant/exp/910_2.png index 47dfe4ac7b0..751585f5b0b 100644 Binary files a/public/images/pokemon/variant/exp/910_2.png and b/public/images/pokemon/variant/exp/910_2.png differ diff --git a/public/images/pokemon/variant/exp/910_3.png b/public/images/pokemon/variant/exp/910_3.png index 3206742c7d8..39b728c4350 100644 Binary files a/public/images/pokemon/variant/exp/910_3.png and b/public/images/pokemon/variant/exp/910_3.png differ diff --git a/public/images/pokemon/variant/exp/911_2.png b/public/images/pokemon/variant/exp/911_2.png index 78cf3658061..af9f0237ccf 100644 Binary files a/public/images/pokemon/variant/exp/911_2.png and b/public/images/pokemon/variant/exp/911_2.png differ diff --git a/public/images/pokemon/variant/exp/911_3.png b/public/images/pokemon/variant/exp/911_3.png index d4c2e86b7d7..4280cbdc4a3 100644 Binary files a/public/images/pokemon/variant/exp/911_3.png and b/public/images/pokemon/variant/exp/911_3.png differ diff --git a/public/images/pokemon/variant/exp/912_3.png b/public/images/pokemon/variant/exp/912_3.png index 846915f851e..211af0da412 100644 Binary files a/public/images/pokemon/variant/exp/912_3.png and b/public/images/pokemon/variant/exp/912_3.png differ diff --git a/public/images/pokemon/variant/exp/913_3.png b/public/images/pokemon/variant/exp/913_3.png index acb749c995d..f18a8e917eb 100644 Binary files a/public/images/pokemon/variant/exp/913_3.png and b/public/images/pokemon/variant/exp/913_3.png differ diff --git a/public/images/pokemon/variant/exp/914_2.png b/public/images/pokemon/variant/exp/914_2.png index 08093e2b50b..66aa120f002 100644 Binary files a/public/images/pokemon/variant/exp/914_2.png and b/public/images/pokemon/variant/exp/914_2.png differ diff --git a/public/images/pokemon/variant/exp/925-four_2.png b/public/images/pokemon/variant/exp/925-four_2.png index 1a6403a6673..3a2f00f0606 100644 Binary files a/public/images/pokemon/variant/exp/925-four_2.png and b/public/images/pokemon/variant/exp/925-four_2.png differ diff --git a/public/images/pokemon/variant/exp/925-four_3.png b/public/images/pokemon/variant/exp/925-four_3.png index 41972571610..51496f55c18 100644 Binary files a/public/images/pokemon/variant/exp/925-four_3.png and b/public/images/pokemon/variant/exp/925-four_3.png differ diff --git a/public/images/pokemon/variant/exp/925-three_2.png b/public/images/pokemon/variant/exp/925-three_2.png index 2bfb7863223..f571d24f033 100644 Binary files a/public/images/pokemon/variant/exp/925-three_2.png and b/public/images/pokemon/variant/exp/925-three_2.png differ diff --git a/public/images/pokemon/variant/exp/925-three_3.png b/public/images/pokemon/variant/exp/925-three_3.png index 0b9a4ae7fc5..e6c54ceb519 100644 Binary files a/public/images/pokemon/variant/exp/925-three_3.png and b/public/images/pokemon/variant/exp/925-three_3.png differ diff --git a/public/images/pokemon/variant/exp/936_1.png b/public/images/pokemon/variant/exp/936_1.png index 980f82162ec..cdc333ab841 100644 Binary files a/public/images/pokemon/variant/exp/936_1.png and b/public/images/pokemon/variant/exp/936_1.png differ diff --git a/public/images/pokemon/variant/exp/936_3.png b/public/images/pokemon/variant/exp/936_3.png index 9a9b984a6ab..27874a25886 100644 Binary files a/public/images/pokemon/variant/exp/936_3.png and b/public/images/pokemon/variant/exp/936_3.png differ diff --git a/public/images/pokemon/variant/exp/937_1.png b/public/images/pokemon/variant/exp/937_1.png index 53fff3c074b..82fa70b0c5b 100644 Binary files a/public/images/pokemon/variant/exp/937_1.png and b/public/images/pokemon/variant/exp/937_1.png differ diff --git a/public/images/pokemon/variant/exp/937_2.png b/public/images/pokemon/variant/exp/937_2.png index af4a9c79eee..43e2b060319 100644 Binary files a/public/images/pokemon/variant/exp/937_2.png and b/public/images/pokemon/variant/exp/937_2.png differ diff --git a/public/images/pokemon/variant/exp/937_3.png b/public/images/pokemon/variant/exp/937_3.png index 5917cc01076..9a208ba1457 100644 Binary files a/public/images/pokemon/variant/exp/937_3.png and b/public/images/pokemon/variant/exp/937_3.png differ diff --git a/public/images/pokemon/variant/exp/94-mega_1.png b/public/images/pokemon/variant/exp/94-mega_1.png index f37635c0c8b..78787902b23 100644 Binary files a/public/images/pokemon/variant/exp/94-mega_1.png and b/public/images/pokemon/variant/exp/94-mega_1.png differ diff --git a/public/images/pokemon/variant/exp/94-mega_2.png b/public/images/pokemon/variant/exp/94-mega_2.png index 264672eb70b..c4ac996ff52 100644 Binary files a/public/images/pokemon/variant/exp/94-mega_2.png and b/public/images/pokemon/variant/exp/94-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/94-mega_3.png b/public/images/pokemon/variant/exp/94-mega_3.png index 89965a8a68f..f63c504e903 100644 Binary files a/public/images/pokemon/variant/exp/94-mega_3.png and b/public/images/pokemon/variant/exp/94-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/970_2.png b/public/images/pokemon/variant/exp/970_2.png index 23b7304a188..e7a046c2d7f 100644 Binary files a/public/images/pokemon/variant/exp/970_2.png and b/public/images/pokemon/variant/exp/970_2.png differ diff --git a/public/images/pokemon/variant/exp/970_3.png b/public/images/pokemon/variant/exp/970_3.png index 4513e32e924..213268b4d44 100644 Binary files a/public/images/pokemon/variant/exp/970_3.png and b/public/images/pokemon/variant/exp/970_3.png differ diff --git a/public/images/pokemon/variant/exp/978-curly_2.png b/public/images/pokemon/variant/exp/978-curly_2.png index 7fde5dc7ff4..800d4b3ee48 100644 Binary files a/public/images/pokemon/variant/exp/978-curly_2.png and b/public/images/pokemon/variant/exp/978-curly_2.png differ diff --git a/public/images/pokemon/variant/exp/978-curly_3.png b/public/images/pokemon/variant/exp/978-curly_3.png index 61f28cd014c..0625741b052 100644 Binary files a/public/images/pokemon/variant/exp/978-curly_3.png and b/public/images/pokemon/variant/exp/978-curly_3.png differ diff --git a/public/images/pokemon/variant/exp/978-droopy_2.png b/public/images/pokemon/variant/exp/978-droopy_2.png index c1570d64cc4..3ece2e41e47 100644 Binary files a/public/images/pokemon/variant/exp/978-droopy_2.png and b/public/images/pokemon/variant/exp/978-droopy_2.png differ diff --git a/public/images/pokemon/variant/exp/978-droopy_3.png b/public/images/pokemon/variant/exp/978-droopy_3.png index d64226dfb45..f1134506f3b 100644 Binary files a/public/images/pokemon/variant/exp/978-droopy_3.png and b/public/images/pokemon/variant/exp/978-droopy_3.png differ diff --git a/public/images/pokemon/variant/exp/978-stretchy_2.png b/public/images/pokemon/variant/exp/978-stretchy_2.png index 38fb36d785f..62d37a3e66a 100644 Binary files a/public/images/pokemon/variant/exp/978-stretchy_2.png and b/public/images/pokemon/variant/exp/978-stretchy_2.png differ diff --git a/public/images/pokemon/variant/exp/978-stretchy_3.png b/public/images/pokemon/variant/exp/978-stretchy_3.png index 1a2ded1841e..608ec2697cc 100644 Binary files a/public/images/pokemon/variant/exp/978-stretchy_3.png and b/public/images/pokemon/variant/exp/978-stretchy_3.png differ diff --git a/public/images/pokemon/variant/exp/979_1.png b/public/images/pokemon/variant/exp/979_1.png index 9d07620bf16..89c3a37d4aa 100644 Binary files a/public/images/pokemon/variant/exp/979_1.png and b/public/images/pokemon/variant/exp/979_1.png differ diff --git a/public/images/pokemon/variant/exp/979_2.png b/public/images/pokemon/variant/exp/979_2.png index 07c6ece9b07..788de4b089d 100644 Binary files a/public/images/pokemon/variant/exp/979_2.png and b/public/images/pokemon/variant/exp/979_2.png differ diff --git a/public/images/pokemon/variant/exp/979_3.png b/public/images/pokemon/variant/exp/979_3.png index da84b866c39..b0e6826c6c1 100644 Binary files a/public/images/pokemon/variant/exp/979_3.png and b/public/images/pokemon/variant/exp/979_3.png differ diff --git a/public/images/pokemon/variant/exp/994_3.png b/public/images/pokemon/variant/exp/994_3.png index a9f3f7fddc2..abd17460bc7 100644 Binary files a/public/images/pokemon/variant/exp/994_3.png and b/public/images/pokemon/variant/exp/994_3.png differ diff --git a/public/images/pokemon/variant/exp/997_2.png b/public/images/pokemon/variant/exp/997_2.png index 42065f31cad..6c61c142d9a 100644 Binary files a/public/images/pokemon/variant/exp/997_2.png and b/public/images/pokemon/variant/exp/997_2.png differ diff --git a/public/images/pokemon/variant/exp/997_3.png b/public/images/pokemon/variant/exp/997_3.png index 27ccadb26f8..c25719e15c7 100644 Binary files a/public/images/pokemon/variant/exp/997_3.png and b/public/images/pokemon/variant/exp/997_3.png differ diff --git a/public/images/pokemon/variant/exp/998_2.png b/public/images/pokemon/variant/exp/998_2.png index e79ad7c367b..ec371e4d986 100644 Binary files a/public/images/pokemon/variant/exp/998_2.png and b/public/images/pokemon/variant/exp/998_2.png differ diff --git a/public/images/pokemon/variant/exp/998_3.png b/public/images/pokemon/variant/exp/998_3.png index 19d7cc76f86..92db876caad 100644 Binary files a/public/images/pokemon/variant/exp/998_3.png and b/public/images/pokemon/variant/exp/998_3.png differ diff --git a/public/images/pokemon/variant/exp/999_1.png b/public/images/pokemon/variant/exp/999_1.png index c5092e856ce..df33c59f607 100644 Binary files a/public/images/pokemon/variant/exp/999_1.png and b/public/images/pokemon/variant/exp/999_1.png differ diff --git a/public/images/pokemon/variant/exp/back/181-mega_3.png b/public/images/pokemon/variant/exp/back/181-mega_3.png index 81856e501e1..771ce25a0da 100644 Binary files a/public/images/pokemon/variant/exp/back/181-mega_3.png and b/public/images/pokemon/variant/exp/back/181-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/back/212-mega_2.png b/public/images/pokemon/variant/exp/back/212-mega_2.png index d4337df3477..877ede02ad4 100644 Binary files a/public/images/pokemon/variant/exp/back/212-mega_2.png and b/public/images/pokemon/variant/exp/back/212-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/back/212-mega_3.png b/public/images/pokemon/variant/exp/back/212-mega_3.png index 173fb44c257..68db47ab830 100644 Binary files a/public/images/pokemon/variant/exp/back/212-mega_3.png and b/public/images/pokemon/variant/exp/back/212-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/back/248-mega_2.png b/public/images/pokemon/variant/exp/back/248-mega_2.png index 6dd5a1a7111..7971c22d8fe 100644 Binary files a/public/images/pokemon/variant/exp/back/248-mega_2.png and b/public/images/pokemon/variant/exp/back/248-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/back/3-mega_2.png b/public/images/pokemon/variant/exp/back/3-mega_2.png index 12a633ba7fa..777f4d0bca5 100644 Binary files a/public/images/pokemon/variant/exp/back/3-mega_2.png and b/public/images/pokemon/variant/exp/back/3-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/back/3-mega_3.png b/public/images/pokemon/variant/exp/back/3-mega_3.png index dbca39c55e6..4684c989e3d 100644 Binary files a/public/images/pokemon/variant/exp/back/3-mega_3.png and b/public/images/pokemon/variant/exp/back/3-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/back/4080_1.png b/public/images/pokemon/variant/exp/back/4080_1.png index 580548f6d3a..2bed3a5b4c1 100644 Binary files a/public/images/pokemon/variant/exp/back/4080_1.png and b/public/images/pokemon/variant/exp/back/4080_1.png differ diff --git a/public/images/pokemon/variant/exp/back/4080_2.png b/public/images/pokemon/variant/exp/back/4080_2.png index 988ac3f7834..e0fa30094cf 100644 Binary files a/public/images/pokemon/variant/exp/back/4080_2.png and b/public/images/pokemon/variant/exp/back/4080_2.png differ diff --git a/public/images/pokemon/variant/exp/back/4080_3.png b/public/images/pokemon/variant/exp/back/4080_3.png index 0fb239266ec..ed11f7ab90c 100644 Binary files a/public/images/pokemon/variant/exp/back/4080_3.png and b/public/images/pokemon/variant/exp/back/4080_3.png differ diff --git a/public/images/pokemon/variant/exp/back/4199_1.png b/public/images/pokemon/variant/exp/back/4199_1.png index 17e691cb35b..eb658cc2171 100644 Binary files a/public/images/pokemon/variant/exp/back/4199_1.png and b/public/images/pokemon/variant/exp/back/4199_1.png differ diff --git a/public/images/pokemon/variant/exp/back/665_2.png b/public/images/pokemon/variant/exp/back/665_2.png index bfddd085cc2..83762b9392c 100644 Binary files a/public/images/pokemon/variant/exp/back/665_2.png and b/public/images/pokemon/variant/exp/back/665_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-archipelago_2.png b/public/images/pokemon/variant/exp/back/666-archipelago_2.png index 032fd73f5c3..86e17e33f11 100644 Binary files a/public/images/pokemon/variant/exp/back/666-archipelago_2.png and b/public/images/pokemon/variant/exp/back/666-archipelago_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-archipelago_3.png b/public/images/pokemon/variant/exp/back/666-archipelago_3.png index b14fc9699cb..7028f550d45 100644 Binary files a/public/images/pokemon/variant/exp/back/666-archipelago_3.png and b/public/images/pokemon/variant/exp/back/666-archipelago_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-continental_2.png b/public/images/pokemon/variant/exp/back/666-continental_2.png index df588f7c4f5..3b9a31469af 100644 Binary files a/public/images/pokemon/variant/exp/back/666-continental_2.png and b/public/images/pokemon/variant/exp/back/666-continental_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-continental_3.png b/public/images/pokemon/variant/exp/back/666-continental_3.png index 76bb3953e51..b61b16538eb 100644 Binary files a/public/images/pokemon/variant/exp/back/666-continental_3.png and b/public/images/pokemon/variant/exp/back/666-continental_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-elegant_2.png b/public/images/pokemon/variant/exp/back/666-elegant_2.png index 68129e87db0..315b2fe859d 100644 Binary files a/public/images/pokemon/variant/exp/back/666-elegant_2.png and b/public/images/pokemon/variant/exp/back/666-elegant_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-elegant_3.png b/public/images/pokemon/variant/exp/back/666-elegant_3.png index d68e8977850..490bbb5951c 100644 Binary files a/public/images/pokemon/variant/exp/back/666-elegant_3.png and b/public/images/pokemon/variant/exp/back/666-elegant_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-fancy_2.png b/public/images/pokemon/variant/exp/back/666-fancy_2.png index 1fce2d851f4..a92f1e7eecf 100644 Binary files a/public/images/pokemon/variant/exp/back/666-fancy_2.png and b/public/images/pokemon/variant/exp/back/666-fancy_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-fancy_3.png b/public/images/pokemon/variant/exp/back/666-fancy_3.png index 8bb80556451..7a54e9bb6cd 100644 Binary files a/public/images/pokemon/variant/exp/back/666-fancy_3.png and b/public/images/pokemon/variant/exp/back/666-fancy_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-garden_2.png b/public/images/pokemon/variant/exp/back/666-garden_2.png index 547bcb72245..c56ff652c7d 100644 Binary files a/public/images/pokemon/variant/exp/back/666-garden_2.png and b/public/images/pokemon/variant/exp/back/666-garden_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-garden_3.png b/public/images/pokemon/variant/exp/back/666-garden_3.png index 8b0261631c3..60c7f5b7d01 100644 Binary files a/public/images/pokemon/variant/exp/back/666-garden_3.png and b/public/images/pokemon/variant/exp/back/666-garden_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-high-plains_2.png b/public/images/pokemon/variant/exp/back/666-high-plains_2.png index d656a17d3cd..d53634ed092 100644 Binary files a/public/images/pokemon/variant/exp/back/666-high-plains_2.png and b/public/images/pokemon/variant/exp/back/666-high-plains_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-high-plains_3.png b/public/images/pokemon/variant/exp/back/666-high-plains_3.png index cc4d6aa7536..d6c577be131 100644 Binary files a/public/images/pokemon/variant/exp/back/666-high-plains_3.png and b/public/images/pokemon/variant/exp/back/666-high-plains_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-icy-snow_2.png b/public/images/pokemon/variant/exp/back/666-icy-snow_2.png index f5b9bff1f1a..02e7e2e61af 100644 Binary files a/public/images/pokemon/variant/exp/back/666-icy-snow_2.png and b/public/images/pokemon/variant/exp/back/666-icy-snow_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-icy-snow_3.png b/public/images/pokemon/variant/exp/back/666-icy-snow_3.png index 3b9a184ff14..3c247e0d43a 100644 Binary files a/public/images/pokemon/variant/exp/back/666-icy-snow_3.png and b/public/images/pokemon/variant/exp/back/666-icy-snow_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-jungle_2.png b/public/images/pokemon/variant/exp/back/666-jungle_2.png index 64465ae4644..d6023a6500f 100644 Binary files a/public/images/pokemon/variant/exp/back/666-jungle_2.png and b/public/images/pokemon/variant/exp/back/666-jungle_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-jungle_3.png b/public/images/pokemon/variant/exp/back/666-jungle_3.png index e0132ad8b7a..0f4ca358601 100644 Binary files a/public/images/pokemon/variant/exp/back/666-jungle_3.png and b/public/images/pokemon/variant/exp/back/666-jungle_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-marine_2.png b/public/images/pokemon/variant/exp/back/666-marine_2.png index 11f4045e000..ffa21823012 100644 Binary files a/public/images/pokemon/variant/exp/back/666-marine_2.png and b/public/images/pokemon/variant/exp/back/666-marine_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-marine_3.png b/public/images/pokemon/variant/exp/back/666-marine_3.png index f8dbcebb2d2..ee4c51543ee 100644 Binary files a/public/images/pokemon/variant/exp/back/666-marine_3.png and b/public/images/pokemon/variant/exp/back/666-marine_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-meadow_2.png b/public/images/pokemon/variant/exp/back/666-meadow_2.png index 3a66918a8d2..bb54cac34cd 100644 Binary files a/public/images/pokemon/variant/exp/back/666-meadow_2.png and b/public/images/pokemon/variant/exp/back/666-meadow_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-meadow_3.png b/public/images/pokemon/variant/exp/back/666-meadow_3.png index 04c790e6b2b..865e404c1cd 100644 Binary files a/public/images/pokemon/variant/exp/back/666-meadow_3.png and b/public/images/pokemon/variant/exp/back/666-meadow_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-modern_2.png b/public/images/pokemon/variant/exp/back/666-modern_2.png index 1e226e6b79c..4cd5d770881 100644 Binary files a/public/images/pokemon/variant/exp/back/666-modern_2.png and b/public/images/pokemon/variant/exp/back/666-modern_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-modern_3.png b/public/images/pokemon/variant/exp/back/666-modern_3.png index 6d7516c4620..5795174404f 100644 Binary files a/public/images/pokemon/variant/exp/back/666-modern_3.png and b/public/images/pokemon/variant/exp/back/666-modern_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-monsoon_2.png b/public/images/pokemon/variant/exp/back/666-monsoon_2.png index 7570e6a7bfe..8f091f4d1de 100644 Binary files a/public/images/pokemon/variant/exp/back/666-monsoon_2.png and b/public/images/pokemon/variant/exp/back/666-monsoon_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-monsoon_3.png b/public/images/pokemon/variant/exp/back/666-monsoon_3.png index 6e9314cde1f..f216010ca99 100644 Binary files a/public/images/pokemon/variant/exp/back/666-monsoon_3.png and b/public/images/pokemon/variant/exp/back/666-monsoon_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-ocean_2.png b/public/images/pokemon/variant/exp/back/666-ocean_2.png index f8757bc7547..6f4fefc99f8 100644 Binary files a/public/images/pokemon/variant/exp/back/666-ocean_2.png and b/public/images/pokemon/variant/exp/back/666-ocean_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-ocean_3.png b/public/images/pokemon/variant/exp/back/666-ocean_3.png index 77b839e4326..554fcb7b510 100644 Binary files a/public/images/pokemon/variant/exp/back/666-ocean_3.png and b/public/images/pokemon/variant/exp/back/666-ocean_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-poke-ball_2.png b/public/images/pokemon/variant/exp/back/666-poke-ball_2.png index caaf61118bc..a1bd953a1b3 100644 Binary files a/public/images/pokemon/variant/exp/back/666-poke-ball_2.png and b/public/images/pokemon/variant/exp/back/666-poke-ball_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-poke-ball_3.png b/public/images/pokemon/variant/exp/back/666-poke-ball_3.png index 944f53eb4aa..9e7ff12912d 100644 Binary files a/public/images/pokemon/variant/exp/back/666-poke-ball_3.png and b/public/images/pokemon/variant/exp/back/666-poke-ball_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-polar_2.png b/public/images/pokemon/variant/exp/back/666-polar_2.png index e8d5a056bb3..b66daac7d2c 100644 Binary files a/public/images/pokemon/variant/exp/back/666-polar_2.png and b/public/images/pokemon/variant/exp/back/666-polar_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-polar_3.png b/public/images/pokemon/variant/exp/back/666-polar_3.png index 131d702ddb1..cab4822a306 100644 Binary files a/public/images/pokemon/variant/exp/back/666-polar_3.png and b/public/images/pokemon/variant/exp/back/666-polar_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-river_2.png b/public/images/pokemon/variant/exp/back/666-river_2.png index 97ce9b85139..c689a6c7150 100644 Binary files a/public/images/pokemon/variant/exp/back/666-river_2.png and b/public/images/pokemon/variant/exp/back/666-river_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-river_3.png b/public/images/pokemon/variant/exp/back/666-river_3.png index c84413aaaaf..a163e179d0e 100644 Binary files a/public/images/pokemon/variant/exp/back/666-river_3.png and b/public/images/pokemon/variant/exp/back/666-river_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-sandstorm_2.png b/public/images/pokemon/variant/exp/back/666-sandstorm_2.png index 541d183dc62..534e96bee13 100644 Binary files a/public/images/pokemon/variant/exp/back/666-sandstorm_2.png and b/public/images/pokemon/variant/exp/back/666-sandstorm_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-sandstorm_3.png b/public/images/pokemon/variant/exp/back/666-sandstorm_3.png index 393e47eb747..649fa83e025 100644 Binary files a/public/images/pokemon/variant/exp/back/666-sandstorm_3.png and b/public/images/pokemon/variant/exp/back/666-sandstorm_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-savanna_2.png b/public/images/pokemon/variant/exp/back/666-savanna_2.png index dea05ede882..b4b8ecedb9e 100644 Binary files a/public/images/pokemon/variant/exp/back/666-savanna_2.png and b/public/images/pokemon/variant/exp/back/666-savanna_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-savanna_3.png b/public/images/pokemon/variant/exp/back/666-savanna_3.png index bc2366e3483..6bc0a0b8363 100644 Binary files a/public/images/pokemon/variant/exp/back/666-savanna_3.png and b/public/images/pokemon/variant/exp/back/666-savanna_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-sun_2.png b/public/images/pokemon/variant/exp/back/666-sun_2.png index 32737f852e6..c9168d5a1c5 100644 Binary files a/public/images/pokemon/variant/exp/back/666-sun_2.png and b/public/images/pokemon/variant/exp/back/666-sun_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-sun_3.png b/public/images/pokemon/variant/exp/back/666-sun_3.png index d1679b936e1..1a36299851e 100644 Binary files a/public/images/pokemon/variant/exp/back/666-sun_3.png and b/public/images/pokemon/variant/exp/back/666-sun_3.png differ diff --git a/public/images/pokemon/variant/exp/back/666-tundra_2.png b/public/images/pokemon/variant/exp/back/666-tundra_2.png index 7311852570a..49325397d8a 100644 Binary files a/public/images/pokemon/variant/exp/back/666-tundra_2.png and b/public/images/pokemon/variant/exp/back/666-tundra_2.png differ diff --git a/public/images/pokemon/variant/exp/back/666-tundra_3.png b/public/images/pokemon/variant/exp/back/666-tundra_3.png index 3d1d40ca2a2..3284e1e637b 100644 Binary files a/public/images/pokemon/variant/exp/back/666-tundra_3.png and b/public/images/pokemon/variant/exp/back/666-tundra_3.png differ diff --git a/public/images/pokemon/variant/exp/back/696_1.png b/public/images/pokemon/variant/exp/back/696_1.png index daae82216a5..5e68ab9c167 100644 Binary files a/public/images/pokemon/variant/exp/back/696_1.png and b/public/images/pokemon/variant/exp/back/696_1.png differ diff --git a/public/images/pokemon/variant/exp/back/696_2.png b/public/images/pokemon/variant/exp/back/696_2.png index a8183d3c396..17e53dcd881 100644 Binary files a/public/images/pokemon/variant/exp/back/696_2.png and b/public/images/pokemon/variant/exp/back/696_2.png differ diff --git a/public/images/pokemon/variant/exp/back/696_3.png b/public/images/pokemon/variant/exp/back/696_3.png index 1eef16f0612..b6cecfa1c8d 100644 Binary files a/public/images/pokemon/variant/exp/back/696_3.png and b/public/images/pokemon/variant/exp/back/696_3.png differ diff --git a/public/images/pokemon/variant/exp/back/697_3.png b/public/images/pokemon/variant/exp/back/697_3.png index d9f80e85a02..4c916ab0d82 100644 Binary files a/public/images/pokemon/variant/exp/back/697_3.png and b/public/images/pokemon/variant/exp/back/697_3.png differ diff --git a/public/images/pokemon/variant/exp/back/699_2.png b/public/images/pokemon/variant/exp/back/699_2.png index 249930be50f..ea3da153124 100644 Binary files a/public/images/pokemon/variant/exp/back/699_2.png and b/public/images/pokemon/variant/exp/back/699_2.png differ diff --git a/public/images/pokemon/variant/exp/back/699_3.png b/public/images/pokemon/variant/exp/back/699_3.png index f5082d58284..4fd35302b43 100644 Binary files a/public/images/pokemon/variant/exp/back/699_3.png and b/public/images/pokemon/variant/exp/back/699_3.png differ diff --git a/public/images/pokemon/variant/exp/back/729_2.png b/public/images/pokemon/variant/exp/back/729_2.png index 1f2e8f31e2a..d5600f3bee4 100644 Binary files a/public/images/pokemon/variant/exp/back/729_2.png and b/public/images/pokemon/variant/exp/back/729_2.png differ diff --git a/public/images/pokemon/variant/exp/back/729_3.png b/public/images/pokemon/variant/exp/back/729_3.png index 08eb10257e1..182d81a0754 100644 Binary files a/public/images/pokemon/variant/exp/back/729_3.png and b/public/images/pokemon/variant/exp/back/729_3.png differ diff --git a/public/images/pokemon/variant/exp/back/730_2.png b/public/images/pokemon/variant/exp/back/730_2.png index b8340e4fca3..e9090ce19dd 100644 Binary files a/public/images/pokemon/variant/exp/back/730_2.png and b/public/images/pokemon/variant/exp/back/730_2.png differ diff --git a/public/images/pokemon/variant/exp/back/742_2.png b/public/images/pokemon/variant/exp/back/742_2.png index a442d157a0a..805fe7a4431 100644 Binary files a/public/images/pokemon/variant/exp/back/742_2.png and b/public/images/pokemon/variant/exp/back/742_2.png differ diff --git a/public/images/pokemon/variant/exp/back/742_3.png b/public/images/pokemon/variant/exp/back/742_3.png index 4fb526feb83..ddb72c1836a 100644 Binary files a/public/images/pokemon/variant/exp/back/742_3.png and b/public/images/pokemon/variant/exp/back/742_3.png differ diff --git a/public/images/pokemon/variant/exp/back/743_2.png b/public/images/pokemon/variant/exp/back/743_2.png index 33ab2c28ab8..e8b6ac6c82b 100644 Binary files a/public/images/pokemon/variant/exp/back/743_2.png and b/public/images/pokemon/variant/exp/back/743_2.png differ diff --git a/public/images/pokemon/variant/exp/back/743_3.png b/public/images/pokemon/variant/exp/back/743_3.png index 50328efe675..4a55750114d 100644 Binary files a/public/images/pokemon/variant/exp/back/743_3.png and b/public/images/pokemon/variant/exp/back/743_3.png differ diff --git a/public/images/pokemon/variant/exp/back/747_2.png b/public/images/pokemon/variant/exp/back/747_2.png index 50f44c0def7..f0df54539eb 100644 Binary files a/public/images/pokemon/variant/exp/back/747_2.png and b/public/images/pokemon/variant/exp/back/747_2.png differ diff --git a/public/images/pokemon/variant/exp/back/747_3.png b/public/images/pokemon/variant/exp/back/747_3.png index 2fb8e6036b9..7d887899e74 100644 Binary files a/public/images/pokemon/variant/exp/back/747_3.png and b/public/images/pokemon/variant/exp/back/747_3.png differ diff --git a/public/images/pokemon/variant/exp/back/754_2.png b/public/images/pokemon/variant/exp/back/754_2.png index 63f7486da1c..057d90eb009 100644 Binary files a/public/images/pokemon/variant/exp/back/754_2.png and b/public/images/pokemon/variant/exp/back/754_2.png differ diff --git a/public/images/pokemon/variant/exp/back/754_3.png b/public/images/pokemon/variant/exp/back/754_3.png index 205c3648755..3a3c01f7095 100644 Binary files a/public/images/pokemon/variant/exp/back/754_3.png and b/public/images/pokemon/variant/exp/back/754_3.png differ diff --git a/public/images/pokemon/variant/exp/back/776_2.png b/public/images/pokemon/variant/exp/back/776_2.png index 61e3662b56b..84393b04c56 100644 Binary files a/public/images/pokemon/variant/exp/back/776_2.png and b/public/images/pokemon/variant/exp/back/776_2.png differ diff --git a/public/images/pokemon/variant/exp/back/776_3.png b/public/images/pokemon/variant/exp/back/776_3.png index 5094ab508d1..600b00f8636 100644 Binary files a/public/images/pokemon/variant/exp/back/776_3.png and b/public/images/pokemon/variant/exp/back/776_3.png differ diff --git a/public/images/pokemon/variant/exp/back/857_2.png b/public/images/pokemon/variant/exp/back/857_2.png index d6472911e31..b51428bbec2 100644 Binary files a/public/images/pokemon/variant/exp/back/857_2.png and b/public/images/pokemon/variant/exp/back/857_2.png differ diff --git a/public/images/pokemon/variant/exp/back/857_3.png b/public/images/pokemon/variant/exp/back/857_3.png index 27f39ebfaf9..4dc21c5492d 100644 Binary files a/public/images/pokemon/variant/exp/back/857_3.png and b/public/images/pokemon/variant/exp/back/857_3.png differ diff --git a/public/images/pokemon/variant/exp/back/862_2.png b/public/images/pokemon/variant/exp/back/862_2.png index fae2900c566..55fee69723b 100644 Binary files a/public/images/pokemon/variant/exp/back/862_2.png and b/public/images/pokemon/variant/exp/back/862_2.png differ diff --git a/public/images/pokemon/variant/exp/back/862_3.png b/public/images/pokemon/variant/exp/back/862_3.png index 3f4537474f5..b3c9cc656b5 100644 Binary files a/public/images/pokemon/variant/exp/back/862_3.png and b/public/images/pokemon/variant/exp/back/862_3.png differ diff --git a/public/images/pokemon/variant/exp/back/9-mega_2.png b/public/images/pokemon/variant/exp/back/9-mega_2.png index e961ace2e5c..02987564bea 100644 Binary files a/public/images/pokemon/variant/exp/back/9-mega_2.png and b/public/images/pokemon/variant/exp/back/9-mega_2.png differ diff --git a/public/images/pokemon/variant/exp/back/9-mega_3.png b/public/images/pokemon/variant/exp/back/9-mega_3.png index 95a7babe58b..00150d4dd46 100644 Binary files a/public/images/pokemon/variant/exp/back/9-mega_3.png and b/public/images/pokemon/variant/exp/back/9-mega_3.png differ diff --git a/public/images/pokemon/variant/exp/back/910_2.png b/public/images/pokemon/variant/exp/back/910_2.png index 851bfd64563..2fa214c4e35 100644 Binary files a/public/images/pokemon/variant/exp/back/910_2.png and b/public/images/pokemon/variant/exp/back/910_2.png differ diff --git a/public/images/pokemon/variant/exp/back/910_3.png b/public/images/pokemon/variant/exp/back/910_3.png index 168db41336e..1027adef03f 100644 Binary files a/public/images/pokemon/variant/exp/back/910_3.png and b/public/images/pokemon/variant/exp/back/910_3.png differ diff --git a/public/images/pokemon/variant/exp/back/914_2.png b/public/images/pokemon/variant/exp/back/914_2.png index 5dee5c12f76..12bdc769c53 100644 Binary files a/public/images/pokemon/variant/exp/back/914_2.png and b/public/images/pokemon/variant/exp/back/914_2.png differ diff --git a/public/images/pokemon/variant/exp/back/914_3.png b/public/images/pokemon/variant/exp/back/914_3.png index 0185c529f70..5304b52d91a 100644 Binary files a/public/images/pokemon/variant/exp/back/914_3.png and b/public/images/pokemon/variant/exp/back/914_3.png differ diff --git a/public/images/pokemon/variant/exp/back/925-four_2.png b/public/images/pokemon/variant/exp/back/925-four_2.png index 6016d30d27c..9dd508f1cdb 100644 Binary files a/public/images/pokemon/variant/exp/back/925-four_2.png and b/public/images/pokemon/variant/exp/back/925-four_2.png differ diff --git a/public/images/pokemon/variant/exp/back/925-four_3.png b/public/images/pokemon/variant/exp/back/925-four_3.png index 3bc6a158fd5..74bb7d3385c 100644 Binary files a/public/images/pokemon/variant/exp/back/925-four_3.png and b/public/images/pokemon/variant/exp/back/925-four_3.png differ diff --git a/public/images/pokemon/variant/exp/back/925-three_2.png b/public/images/pokemon/variant/exp/back/925-three_2.png index 789e5afb20a..e2303d720b9 100644 Binary files a/public/images/pokemon/variant/exp/back/925-three_2.png and b/public/images/pokemon/variant/exp/back/925-three_2.png differ diff --git a/public/images/pokemon/variant/exp/back/925-three_3.png b/public/images/pokemon/variant/exp/back/925-three_3.png index cc1a1d3be11..8d568cce517 100644 Binary files a/public/images/pokemon/variant/exp/back/925-three_3.png and b/public/images/pokemon/variant/exp/back/925-three_3.png differ diff --git a/public/images/pokemon/variant/exp/back/936_1.png b/public/images/pokemon/variant/exp/back/936_1.png index 2b9e2d533f8..154c4e86364 100644 Binary files a/public/images/pokemon/variant/exp/back/936_1.png and b/public/images/pokemon/variant/exp/back/936_1.png differ diff --git a/public/images/pokemon/variant/exp/back/936_2.png b/public/images/pokemon/variant/exp/back/936_2.png index 45fb506fd01..442abc72971 100644 Binary files a/public/images/pokemon/variant/exp/back/936_2.png and b/public/images/pokemon/variant/exp/back/936_2.png differ diff --git a/public/images/pokemon/variant/exp/back/936_3.png b/public/images/pokemon/variant/exp/back/936_3.png index 82d3c0ecb0b..db67191d73e 100644 Binary files a/public/images/pokemon/variant/exp/back/936_3.png and b/public/images/pokemon/variant/exp/back/936_3.png differ diff --git a/public/images/pokemon/variant/exp/back/937_1.png b/public/images/pokemon/variant/exp/back/937_1.png index c0668be0103..2d0d58b0912 100644 Binary files a/public/images/pokemon/variant/exp/back/937_1.png and b/public/images/pokemon/variant/exp/back/937_1.png differ diff --git a/public/images/pokemon/variant/exp/back/937_2.png b/public/images/pokemon/variant/exp/back/937_2.png index f5bfd790b75..7ba1aa09b97 100644 Binary files a/public/images/pokemon/variant/exp/back/937_2.png and b/public/images/pokemon/variant/exp/back/937_2.png differ diff --git a/public/images/pokemon/variant/exp/back/937_3.png b/public/images/pokemon/variant/exp/back/937_3.png index c0a2be1ae0e..ee875356334 100644 Binary files a/public/images/pokemon/variant/exp/back/937_3.png and b/public/images/pokemon/variant/exp/back/937_3.png differ diff --git a/public/images/pokemon/variant/exp/back/952_2.png b/public/images/pokemon/variant/exp/back/952_2.png index 2518adc55ed..403030f2e3d 100644 Binary files a/public/images/pokemon/variant/exp/back/952_2.png and b/public/images/pokemon/variant/exp/back/952_2.png differ diff --git a/public/images/pokemon/variant/exp/back/968_2.png b/public/images/pokemon/variant/exp/back/968_2.png index 30b92268fbe..726c305140e 100644 Binary files a/public/images/pokemon/variant/exp/back/968_2.png and b/public/images/pokemon/variant/exp/back/968_2.png differ diff --git a/public/images/pokemon/variant/exp/back/968_3.png b/public/images/pokemon/variant/exp/back/968_3.png index e066f0eceef..541c3cf2d84 100644 Binary files a/public/images/pokemon/variant/exp/back/968_3.png and b/public/images/pokemon/variant/exp/back/968_3.png differ diff --git a/public/images/pokemon/variant/exp/back/970_2.png b/public/images/pokemon/variant/exp/back/970_2.png index dd61d57f2ac..5c7d2650363 100644 Binary files a/public/images/pokemon/variant/exp/back/970_2.png and b/public/images/pokemon/variant/exp/back/970_2.png differ diff --git a/public/images/pokemon/variant/exp/back/970_3.png b/public/images/pokemon/variant/exp/back/970_3.png index 025f88e1cbe..592c1f21961 100644 Binary files a/public/images/pokemon/variant/exp/back/970_3.png and b/public/images/pokemon/variant/exp/back/970_3.png differ diff --git a/public/images/pokemon/variant/exp/back/978-curly_2.png b/public/images/pokemon/variant/exp/back/978-curly_2.png index 205f55bf3dc..1ed3505ceee 100644 Binary files a/public/images/pokemon/variant/exp/back/978-curly_2.png and b/public/images/pokemon/variant/exp/back/978-curly_2.png differ diff --git a/public/images/pokemon/variant/exp/back/978-curly_3.png b/public/images/pokemon/variant/exp/back/978-curly_3.png index bee54677b74..dcc2129f72b 100644 Binary files a/public/images/pokemon/variant/exp/back/978-curly_3.png and b/public/images/pokemon/variant/exp/back/978-curly_3.png differ diff --git a/public/images/pokemon/variant/exp/back/978-droopy_2.png b/public/images/pokemon/variant/exp/back/978-droopy_2.png index ddd0430bca6..ed0999c43ca 100644 Binary files a/public/images/pokemon/variant/exp/back/978-droopy_2.png and b/public/images/pokemon/variant/exp/back/978-droopy_2.png differ diff --git a/public/images/pokemon/variant/exp/back/978-droopy_3.png b/public/images/pokemon/variant/exp/back/978-droopy_3.png index 6fa78fb9fef..22416861830 100644 Binary files a/public/images/pokemon/variant/exp/back/978-droopy_3.png and b/public/images/pokemon/variant/exp/back/978-droopy_3.png differ diff --git a/public/images/pokemon/variant/female/3_2.png b/public/images/pokemon/variant/female/3_2.png index df83d7b05e2..8e955e2c82f 100644 Binary files a/public/images/pokemon/variant/female/3_2.png and b/public/images/pokemon/variant/female/3_2.png differ diff --git a/public/images/pokemon/variant/female/402_2.png b/public/images/pokemon/variant/female/402_2.png index 3ce28cc787a..b9fd36890a3 100644 Binary files a/public/images/pokemon/variant/female/402_2.png and b/public/images/pokemon/variant/female/402_2.png differ diff --git a/public/images/pokemon/variant/female/402_3.png b/public/images/pokemon/variant/female/402_3.png index a34ef79b0ff..5e43029bfdb 100644 Binary files a/public/images/pokemon/variant/female/402_3.png and b/public/images/pokemon/variant/female/402_3.png differ diff --git a/public/images/pokemon/variant/female/419_2.png b/public/images/pokemon/variant/female/419_2.png index 897efbd57ba..ca9ea1d6f72 100644 Binary files a/public/images/pokemon/variant/female/419_2.png and b/public/images/pokemon/variant/female/419_2.png differ diff --git a/public/images/pokemon_icons_1.json b/public/images/pokemon_icons_1.json index 49e471514cd..12e26b380a5 100644 --- a/public/images/pokemon_icons_1.json +++ b/public/images/pokemon_icons_1.json @@ -1647,6 +1647,27 @@ "h": 25 } }, + { + "filename": "85-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 5, + "y": 3, + "w": 29, + "h": 25 + }, + "frame": { + "x": 55, + "y": 270, + "w": 29, + "h": 25 + } + }, { "filename": "22s", "rotated": false, @@ -1731,6 +1752,27 @@ "h": 25 } }, + { + "filename": "85s-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 5, + "y": 3, + "w": 29, + "h": 25 + }, + "frame": { + "x": 56, + "y": 317, + "w": 29, + "h": 25 + } + }, { "filename": "9s", "rotated": false, @@ -6456,6 +6498,27 @@ "h": 18 } }, + { + "filename": "84-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 10, + "w": 21, + "h": 18 + }, + "frame": { + "x": 98, + "y": 712, + "w": 21, + "h": 18 + } + }, { "filename": "107", "rotated": false, @@ -6519,6 +6582,27 @@ "h": 18 } }, + { + "filename": "84s-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 9, + "y": 10, + "w": 21, + "h": 18 + }, + "frame": { + "x": 96, + "y": 770, + "w": 21, + "h": 18 + } + }, { "filename": "88", "rotated": false, diff --git a/public/images/pokemon_icons_2.json b/public/images/pokemon_icons_2.json index 5a389362bc0..c5ebfe61487 100644 --- a/public/images/pokemon_icons_2.json +++ b/public/images/pokemon_icons_2.json @@ -786,6 +786,27 @@ "h": 27 } }, + { + "filename": "154-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 1, + "w": 23, + "h": 27 + }, + "frame": { + "x": 29, + "y": 147, + "w": 23, + "h": 27 + } + }, { "filename": "154s", "rotated": false, @@ -807,6 +828,27 @@ "h": 27 } }, + { + "filename": "154s-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 1, + "w": 23, + "h": 27 + }, + "frame": { + "x": 29, + "y": 174, + "w": 23, + "h": 27 + } + }, { "filename": "229-mega", "rotated": false, diff --git a/public/images/pokemon_icons_3.json b/public/images/pokemon_icons_3.json index 220d91f5222..a1aefa0ff0b 100644 --- a/public/images/pokemon_icons_3.json +++ b/public/images/pokemon_icons_3.json @@ -198,6 +198,27 @@ "h": 27 } }, + { + "filename": "257-f-mega", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 32, + "h": 27 + }, + "frame": { + "x": 0, + "y": 79, + "w": 32, + "h": 27 + } + }, { "filename": "257s-mega", "rotated": false, @@ -219,6 +240,27 @@ "h": 27 } }, + { + "filename": "257s-f-mega", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 32, + "h": 27 + }, + "frame": { + "x": 0, + "y": 106, + "w": 32, + "h": 27 + } + }, { "filename": "323-mega", "rotated": false, @@ -1248,6 +1290,27 @@ "h": 26 } }, + { + "filename": "257-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 2, + "w": 25, + "h": 26 + }, + "frame": { + "x": 28, + "y": 556, + "w": 25, + "h": 26 + } + }, { "filename": "257s", "rotated": false, @@ -1269,6 +1332,27 @@ "h": 26 } }, + { + "filename": "257s-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 7, + "y": 2, + "w": 25, + "h": 26 + }, + "frame": { + "x": 28, + "y": 582, + "w": 25, + "h": 26 + } + }, { "filename": "359-mega", "rotated": false, @@ -1605,6 +1689,27 @@ "h": 25 } }, + { + "filename": "256-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 8, + "y": 3, + "w": 23, + "h": 25 + }, + "frame": { + "x": 98, + "y": 72, + "w": 23, + "h": 25 + } + }, { "filename": "282s-mega", "rotated": false, @@ -5553,6 +5658,27 @@ "h": 19 } }, + { + "filename": "255-f", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 40, + "h": 30 + }, + "spriteSourceSize": { + "x": 13, + "y": 9, + "w": 13, + "h": 19 + }, + "frame": { + "x": 204, + "y": 342, + "w": 13, + "h": 19 + } + }, { "filename": "307s", "rotated": false, diff --git a/public/locales b/public/locales index fc4a1effd51..7ad20e64caa 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef +Subproject commit 7ad20e64caa9367b444712f10036fa9bbe4837a5 diff --git a/src/@types/PokerogueAccountApi.ts b/src/@types/PokerogueAccountApi.ts new file mode 100644 index 00000000000..68d0a5e7730 --- /dev/null +++ b/src/@types/PokerogueAccountApi.ts @@ -0,0 +1,17 @@ +import type { UserInfo } from "#app/@types/UserInfo"; + +export interface AccountInfoResponse extends UserInfo {} + +export interface AccountLoginRequest { + username: string; + password: string; +} + +export interface AccountLoginResponse { + token: string; +} + +export interface AccountRegisterRequest { + username: string; + password: string; +} diff --git a/src/@types/PokerogueAdminApi.ts b/src/@types/PokerogueAdminApi.ts new file mode 100644 index 00000000000..2ee25b560d9 --- /dev/null +++ b/src/@types/PokerogueAdminApi.ts @@ -0,0 +1,31 @@ +export interface LinkAccountToDiscordIdRequest { + username: string; + discordId: string; +} + +export interface UnlinkAccountFromDiscordIdRequest { + username: string; + discordId: string; +} + +export interface LinkAccountToGoogledIdRequest { + username: string; + googleId: string; +} + +export interface UnlinkAccountFromGoogledIdRequest { + username: string; + googleId: string; +} + +export interface SearchAccountRequest { + username: string; +} + +export interface SearchAccountResponse { + username: string; + discordId: string; + googleId: string; + lastLoggedIn: string; + registered: string; +} diff --git a/src/@types/PokerogueApi.ts b/src/@types/PokerogueApi.ts new file mode 100644 index 00000000000..79755b23a54 --- /dev/null +++ b/src/@types/PokerogueApi.ts @@ -0,0 +1,4 @@ +export interface TitleStatsResponse { + playerCount: number; + battleCount: number; +} diff --git a/src/@types/PokerogueDailyApi.ts b/src/@types/PokerogueDailyApi.ts new file mode 100644 index 00000000000..3f3d8eb61ca --- /dev/null +++ b/src/@types/PokerogueDailyApi.ts @@ -0,0 +1,10 @@ +import type { ScoreboardCategory } from "#app/ui/daily-run-scoreboard"; + +export interface GetDailyRankingsRequest { + category: ScoreboardCategory; + page?: number; +} + +export interface GetDailyRankingsPageCountRequest { + category: ScoreboardCategory; +} diff --git a/src/@types/PokerogueSavedataApi.ts b/src/@types/PokerogueSavedataApi.ts new file mode 100644 index 00000000000..a313cd708c7 --- /dev/null +++ b/src/@types/PokerogueSavedataApi.ts @@ -0,0 +1,8 @@ +import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; + +export interface UpdateAllSavedataRequest { + system: SystemSaveData; + session: SessionSaveData; + sessionSlotId: number; + clientSessionId: string; +} diff --git a/src/@types/PokerogueSessionSavedataApi.ts b/src/@types/PokerogueSessionSavedataApi.ts new file mode 100644 index 00000000000..c4650611c4f --- /dev/null +++ b/src/@types/PokerogueSessionSavedataApi.ts @@ -0,0 +1,40 @@ +export class UpdateSessionSavedataRequest { + slot: number; + trainerId: number; + secretId: number; + clientSessionId: string; +} + +/** This is **NOT** similar to {@linkcode ClearSessionSavedataRequest} */ +export interface NewClearSessionSavedataRequest { + slot: number; + isVictory: boolean; + clientSessionId: string; +} + +export interface GetSessionSavedataRequest { + slot: number; + clientSessionId: string; +} + +export interface DeleteSessionSavedataRequest { + slot: number; + clientSessionId: string; +} + +/** This is **NOT** similar to {@linkcode NewClearSessionSavedataRequest} */ +export interface ClearSessionSavedataRequest { + slot: number; + trainerId: number; + clientSessionId: string; +} + +/** + * Pokerogue API response for path: `/savedata/session/clear` + */ +export interface ClearSessionSavedataResponse { + /** Contains the error message if any occured */ + error?: string; + /** Is `true` if the request was successfully processed */ + success?: boolean; +} diff --git a/src/@types/PokerogueSystemSavedataApi.ts b/src/@types/PokerogueSystemSavedataApi.ts new file mode 100644 index 00000000000..8ce160a5ec2 --- /dev/null +++ b/src/@types/PokerogueSystemSavedataApi.ts @@ -0,0 +1,20 @@ +import type { SystemSaveData } from "#app/system/game-data"; + +export interface GetSystemSavedataRequest { + clientSessionId: string; +} + +export class UpdateSystemSavedataRequest { + clientSessionId: string; + trainerId?: number; + secretId?: number; +} + +export interface VerifySystemSavedataRequest { + clientSessionId: string; +} + +export interface VerifySystemSavedataResponse { + valid: boolean; + systemData: SystemSaveData; +} diff --git a/src/@types/UserInfo.ts b/src/@types/UserInfo.ts new file mode 100644 index 00000000000..c8a0c6ecb26 --- /dev/null +++ b/src/@types/UserInfo.ts @@ -0,0 +1,7 @@ +export interface UserInfo { + username: string; + lastSessionSlot: number; + discordId: string; + googleId: string; + hasAdminRole: boolean; +} diff --git a/src/@types/pokerogue-api.ts b/src/@types/pokerogue-api.ts deleted file mode 100644 index 892869968bb..00000000000 --- a/src/@types/pokerogue-api.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Pokerogue API response for path: `/savedata/session/clear` - */ -export interface PokerogueApiClearSessionData { - /** Contains the error message if any occured */ - error?: string; - /** Is `true` if the request was successfully processed */ - success?: boolean; -} diff --git a/src/account.ts b/src/account.ts index 692ff2b0d81..316645b38ff 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,14 +1,8 @@ +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import type { UserInfo } from "#app/@types/UserInfo"; import { bypassLogin } from "./battle-scene"; import * as Utils from "./utils"; -export interface UserInfo { - username: string; - lastSessionSlot: integer; - discordId: string; - googleId: string; - hasAdminRole: boolean; -} - export let loggedInUser: UserInfo | null = null; // This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting export const clientSessionId = Utils.randomString(32); @@ -43,18 +37,14 @@ export function updateUserInfo(): Promise<[boolean, integer]> { }); return resolve([ true, 200 ]); } - Utils.apiFetch("account/info", true).then(response => { - if (!response.ok) { - resolve([ false, response.status ]); + pokerogueApi.account.getInfo().then(([ accountInfo, status ]) => { + if (!accountInfo) { + resolve([ false, status ]); return; + } else { + loggedInUser = accountInfo; + resolve([ true, 200 ]); } - return response.json(); - }).then(jsonResponse => { - loggedInUser = jsonResponse; - resolve([ true, 200 ]); - }).catch(err => { - console.error(err); - resolve([ false, 500 ]); }); }); } diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d82acec1c20..c430a12ae3e 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -4,8 +4,8 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; -import { PokeballType } from "#app/data/pokeball"; +import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import { PokeballType } from "#enums/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import { Phase } from "#app/phase"; import { initGameSpeed } from "#app/system/game-speed"; @@ -35,10 +35,11 @@ import { Gender } from "#app/data/gender"; import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "#app/ui/ui-theme"; import PokemonData from "#app/system/pokemon-data"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; import { FormChangePhase } from "#app/phases/form-change-phase"; import { getTypeRgb } from "#app/data/type"; +import { Type } from "#enums/type"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; import CharSprite from "#app/ui/char-sprite"; import DamageNumberHandler from "#app/field/damage-number-handler"; @@ -46,7 +47,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { biomeDepths, getBiomeName } from "#app/data/balance/biomes"; import { SceneBase } from "#app/scene-base"; import CandyBar from "#app/ui/candy-bar"; -import { Variant, variantData } from "#app/data/variant"; +import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant"; import { Localizable } from "#app/interfaces/locales"; import Overrides from "#app/overrides"; import { InputsController } from "#app/inputs-controller"; @@ -96,7 +97,9 @@ import { ExpPhase } from "#app/phases/exp-phase"; import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; +import { StatusEffect } from "#enums/status-effect"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -342,6 +345,33 @@ export default class BattleScene extends SceneBase { this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`); } + /** + * Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache} + */ + loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) { + const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey); + if (useExpSprite) { + fileRoot = `exp/${fileRoot}`; + } + let variantConfig = variantData; + fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null); + const variantSet = variantConfig as VariantSet; + if (variantSet && (variant !== undefined && variantSet[variant] === 1)) { + const populateVariantColors = (key: string): Promise => { + return new Promise(resolve => { + if (variantColorCache.hasOwnProperty(key)) { + return resolve(); + } + this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => { + variantColorCache[key] = c; + resolve(); + }); + }); + }; + populateVariantColors(spriteKey); + } + } + async preload() { if (DEBUG_RNG) { const scene = this; @@ -771,7 +801,7 @@ export default class BattleScene extends SceneBase { /** * @returns An array of {@linkcode PlayerPokemon} filtered from the player's party - * that are {@linkcode PlayerPokemon.isAllowedInBattle | allowed in battle}. + * that are {@linkcode Pokemon.isAllowedInBattle | allowed in battle}. */ public getPokemonAllowedInBattle(): PlayerPokemon[] { return this.getPlayerParty().filter(p => p.isAllowedInBattle()); @@ -888,7 +918,7 @@ export default class BattleScene extends SceneBase { return pokemon; } - addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { + addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon { if (Overrides.OPP_LEVEL_OVERRIDE > 0) { level = Overrides.OPP_LEVEL_OVERRIDE; } @@ -898,13 +928,11 @@ export default class BattleScene extends SceneBase { boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1; } - const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource); + const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource); if (Overrides.OPP_FUSION_OVERRIDE) { pokemon.generateFusionSpecies(); } - overrideModifiers(this, false); - overrideHeldItems(this, pokemon, false); if (boss && !dataSource) { const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296)); @@ -1230,33 +1258,64 @@ export default class BattleScene extends SceneBase { newDouble = !!double; } - if (Overrides.BATTLE_TYPE_OVERRIDE === "double") { - newDouble = true; - } - /* Override battles into single only if not fighting with trainers */ - if (newBattleType !== BattleType.TRAINER && Overrides.BATTLE_TYPE_OVERRIDE === "single") { + // Disable double battles on Endless/Endless Spliced Wave 50x boss battles (Introduced 1.2.0) + if (this.gameMode.isEndlessBoss(newWaveIndex)) { newDouble = false; } - const lastBattle = this.currentBattle; + if (!isNullOrUndefined(Overrides.BATTLE_TYPE_OVERRIDE)) { + let doubleOverrideForWave: "single" | "double" | null = null; - if (lastBattle?.double && !newDouble) { - this.tryRemovePhase(p => p instanceof SwitchPhase); + switch (Overrides.BATTLE_TYPE_OVERRIDE) { + case "double": + doubleOverrideForWave = "double"; + break; + case "single": + doubleOverrideForWave = "single"; + break; + case "even-doubles": + doubleOverrideForWave = (newWaveIndex % 2) ? "single" : "double"; + break; + case "odd-doubles": + doubleOverrideForWave = (newWaveIndex % 2) ? "double" : "single"; + break; + } + + if (doubleOverrideForWave === "double") { + newDouble = true; + } + /** + * Override battles into single only if not fighting with trainers. + * @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 | GitHub Issue #1948} + */ + if (newBattleType !== BattleType.TRAINER && doubleOverrideForWave === "single") { + newDouble = false; + } } + const lastBattle = this.currentBattle; + const maxExpLevel = this.getMaxExpLevel(); this.lastEnemyTrainer = lastBattle?.trainer ?? null; this.lastMysteryEncounter = lastBattle?.mysteryEncounter; + if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { + // Disable double battle on mystery encounters (it may be re-enabled as part of encounter) + newDouble = false; + } + + if (lastBattle?.double && !newDouble) { + this.tryRemovePhase(p => p instanceof SwitchPhase); + this.getPlayerField().forEach(p => p.lapseTag(BattlerTagType.COMMANDED)); + } + this.executeWithSeedOffset(() => { this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); }, newWaveIndex << 3, this.waveSeed); this.currentBattle.incrementTurn(this); if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { - // Disable double battle on mystery encounters (it may be re-enabled as part of encounter) - this.currentBattle.double = false; // Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome this.currentBattle.mysteryEncounterType = mysteryEncounterType; } @@ -1278,6 +1337,8 @@ export default class BattleScene extends SceneBase { if (resetArenaState) { this.arena.resetArenaEffects(); + playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); + playerField.forEach((pokemon, p) => { if (pokemon.isOnField()) { this.pushPhase(new ReturnPhase(this, p)); @@ -1388,10 +1449,19 @@ export default class BattleScene extends SceneBase { case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); case Species.PIKACHU: + if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { + return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 + } return Utils.randSeedInt(8); case Species.EEVEE: + if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { + return 0; // No Partner Eevee for Wave 12 Preschoolers + } return Utils.randSeedInt(2); case Species.GRENINJA: + if (this.currentBattle?.battleType === BattleType.TRAINER) { + return 0; // Don't give trainers Battle Bond Greninja + } return Utils.randSeedInt(2); case Species.ZYGARDE: return Utils.randSeedInt(4); @@ -2399,6 +2469,24 @@ export default class BattleScene extends SceneBase { } } + /** + * Tries to add the input phase to index after target phase in the {@linkcode phaseQueue}, else simply calls {@linkcode unshiftPhase()} + * @param phase {@linkcode Phase} the phase to be added + * @param targetPhase {@linkcode Phase} the type of phase to search for in {@linkcode phaseQueue} + * @returns `true` if a `targetPhase` was found to append to + */ + appendToPhase(phase: Phase, targetPhase: Constructor): boolean { + const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof targetPhase); + + if (targetIndex !== -1 && this.phaseQueue.length > targetIndex) { + this.phaseQueue.splice(targetIndex + 1, 0, phase); + return true; + } else { + this.unshiftPhase(phase); + return false; + } + } + /** * Adds a MessagePhase, either to PhaseQueuePrepend or nextCommandPhaseQueue * @param message string for MessagePhase @@ -2563,14 +2651,15 @@ export default class BattleScene extends SceneBase { * The quantity to transfer is automatically capped at how much the recepient can take before reaching the maximum stack size for the item. * A transfer that moves a quantity smaller than what is specified in the transferQuantity parameter is still considered successful. * @param itemModifier {@linkcode PokemonHeldItemModifier} item to transfer (represents the whole stack) - * @param target {@linkcode Pokemon} pokemon recepient in this transfer - * @param playSound {boolean} - * @param transferQuantity {@linkcode integer} how many items of the stack to transfer. Optional, defaults to 1 - * @param instant {boolean} - * @param ignoreUpdate {boolean} - * @returns true if the transfer was successful + * @param target {@linkcode Pokemon} recepient in this transfer + * @param playSound `true` to play a sound when transferring the item + * @param transferQuantity How many items of the stack to transfer. Optional, defaults to `1` + * @param instant ??? (Optional) + * @param ignoreUpdate ??? (Optional) + * @param itemLost If `true`, treat the item's current holder as losing the item (for now, this simply enables Unburden). Default is `true`. + * @returns `true` if the transfer was successful */ - tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: integer = 1, instant?: boolean, ignoreUpdate?: boolean): Promise { + tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise { return new Promise(resolve => { const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; const cancelled = new Utils.BooleanHolder(false); @@ -2603,14 +2692,14 @@ export default class BattleScene extends SceneBase { if (!matchingModifier || this.removeModifier(matchingModifier, !target.isPlayer())) { if (target.isPlayer()) { this.addModifier(newItemModifier, ignoreUpdate, playSound, false, instant).then(() => { - if (source) { + if (source && itemLost) { applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); } resolve(true); }); } else { this.addEnemyModifier(newItemModifier, ignoreUpdate, instant).then(() => { - if (source) { + if (source && itemLost) { applyPostItemLostAbAttrs(PostItemLostAbAttr, source, false); } resolve(true); @@ -2782,7 +2871,15 @@ export default class BattleScene extends SceneBase { }); } - removeModifier(modifier: PersistentModifier, enemy?: boolean): boolean { + /** + * Removes a currently owned item. If the item is stacked, the entire item stack + * gets removed. This function does NOT apply in-battle effects, such as Unburden. + * If in-battle effects are needed, use {@linkcode Pokemon.loseHeldItem} instead. + * @param modifier The item to be removed. + * @param enemy If `true`, remove an item owned by the enemy. If `false`, remove an item owned by the player. Default is `false`. + * @returns `true` if the item exists and was successfully removed, `false` otherwise. + */ + removeModifier(modifier: PersistentModifier, enemy: boolean = false): boolean { const modifiers = !enemy ? this.modifiers : this.enemyModifiers; const modifierIndex = modifiers.indexOf(modifier); if (modifierIndex > -1) { @@ -2954,7 +3051,8 @@ export default class BattleScene extends SceneBase { } validateAchv(achv: Achv, args?: unknown[]): boolean { - if (!this.gameData.achvUnlocks.hasOwnProperty(achv.id) && achv.validate(this, args)) { + if ((!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) + && achv.validate(this, args)) { this.gameData.achvUnlocks[achv.id] = new Date().getTime(); this.ui.achvBar.showAchv(achv); if (vouchers.hasOwnProperty(achv.id)) { @@ -2979,12 +3077,21 @@ export default class BattleScene extends SceneBase { updateGameInfo(): void { const gameInfo = { - playTime: this.sessionPlayTime ? this.sessionPlayTime : 0, + playTime: this.sessionPlayTime ?? 0, gameMode: this.currentBattle ? this.gameMode.getName() : "Title", biome: this.currentBattle ? getBiomeName(this.arena.biomeType) : "", - wave: this.currentBattle?.waveIndex || 0, - party: this.party ? this.party.map(p => { - return { name: p.name, level: p.level }; + wave: this.currentBattle?.waveIndex ?? 0, + party: this.party ? this.party.map((p) => { + return { + name: p.name, + form: p.getFormKey(), + types: p.getTypes().map((type) => Type[type]), + teraType: p.getTeraType() !== Type.UNKNOWN ? Type[p.getTeraType()] : "", + level: p.level, + currentHP: p.hp, + maxHP: p.getMaxHp(), + status: p.status?.effect ? StatusEffect[p.status.effect] : "" + }; }) : [], modeChain: this.ui?.getModeChain() ?? [], }; diff --git a/src/battle.ts b/src/battle.ts index 0356772bb07..75f0dff2534 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -4,7 +4,7 @@ import * as Utils from "./utils"; import Trainer, { TrainerVariant } from "./field/trainer"; import { GameMode } from "./game-mode"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; -import { PokeballType } from "./data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; import Pokemon, { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon"; diff --git a/src/constants.ts b/src/constants.ts index 0b1261ad814..63f00b9f33f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,3 +3,9 @@ export const PLAYER_PARTY_MAX_SIZE: number = 6; /** Whether to use seasonal splash messages in general */ export const USE_SEASONAL_SPLASH_MESSAGES: boolean = false; + +/** Name of the session ID cookie */ +export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId"; + +/** Max value for an integer attribute in {@linkcode SystemSaveData} */ +export const MAX_INT_ATTR_VALUE = 0x80000000; diff --git a/src/data/ability.ts b/src/data/ability.ts index 8eeb5af52b8..7fa046e2369 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,13 +1,13 @@ import Pokemon, { EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; -import { Type } from "./type"; +import { Type } from "#enums/type"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { getPokemonNameWithAffix } from "../messages"; -import { Weather, WeatherType } from "./weather"; -import { BattlerTag, GroundedTag } from "./battler-tags"; -import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; +import { Weather } from "#app/data/weather"; +import { BattlerTag, BattlerTagLapseType, GroundedTag } from "./battler-tags"; +import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, SacrificialAttr, SacrificialAttrOnHit, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; @@ -35,6 +35,9 @@ import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; export class Ability implements Localizable { public id: Abilities; @@ -511,7 +514,11 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr { } applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker) < 2) { + const modifierValue = args.length > 0 + ? (args[0] as Utils.NumberHolder).value + : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); + + if (move instanceof AttackMove && modifierValue < 2) { cancelled.value = true; // Suppresses "No Effect" message (args[0] as Utils.NumberHolder).value = 0; return true; @@ -578,15 +585,11 @@ export class PostDefendAbAttr extends AbAttr { export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const attackPriority = new Utils.IntegerHolder(move.priority); - applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, attackPriority); - applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, simulated, move, attackPriority); - if (move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) { return false; } - if (attackPriority.value > 0 && !move.isMultiTarget()) { + if (move.getPriority(attacker) > 0 && !move.isMultiTarget()) { cancelled.value = true; return true; } @@ -1348,65 +1351,30 @@ export class AddSecondStrikeAbAttr extends PreAttackAbAttr { this.damageMultiplier = damageMultiplier; } - /** - * Determines whether this attribute can apply to a given move. - * @param {Move} move the move to which this attribute may apply - * @param numTargets the number of {@linkcode Pokemon} targeted by this move - * @returns true if the attribute can apply to the move, false otherwise - */ - canApplyPreAttack(move: Move, numTargets: integer): boolean { - /** - * Parental Bond cannot apply to multi-hit moves, charging moves, or - * moves that cause the user to faint. - */ - const exceptAttrs: Constructor[] = [ - MultiHitAttr, - SacrificialAttr, - SacrificialAttrOnHit - ]; - - /** Parental Bond cannot apply to these specific moves */ - const exceptMoves: Moves[] = [ - Moves.FLING, - Moves.UPROAR, - Moves.ROLLOUT, - Moves.ICE_BALL, - Moves.ENDEAVOR - ]; - - /** Also check if this move is an Attack move and if it's only targeting one Pokemon */ - return numTargets === 1 - && !move.isChargingMove() - && !exceptAttrs.some(attr => move.hasAttr(attr)) - && !exceptMoves.some(id => move.id === id) - && move.category !== MoveCategory.STATUS; - } - /** * If conditions are met, this doubles the move's hit count (via args[1]) * or multiplies the damage of secondary strikes (via args[2]) - * @param {Pokemon} pokemon the Pokemon using the move + * @param pokemon the {@linkcode Pokemon} using the move * @param passive n/a * @param defender n/a - * @param {Move} move the move used by the ability source - * @param args\[0\] the number of Pokemon this move is targeting - * @param {Utils.IntegerHolder} args\[1\] the number of strikes with this move - * @param {Utils.NumberHolder} args\[2\] the damage multiplier for the current strike + * @param move the {@linkcode Move} used by the ability source + * @param args Additional arguments: + * - `[0]` the number of strikes this move currently has ({@linkcode Utils.NumberHolder}) + * - `[1]` the damage multiplier for the current strike ({@linkcode Utils.NumberHolder}) * @returns */ applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): boolean { - const numTargets = args[0] as integer; - const hitCount = args[1] as Utils.IntegerHolder; - const multiplier = args[2] as Utils.NumberHolder; + const hitCount = args[0] as Utils.NumberHolder; + const multiplier = args[1] as Utils.NumberHolder; - if (this.canApplyPreAttack(move, numTargets)) { + if (move.canBeMultiStrikeEnhanced(pokemon, true)) { this.showAbility = !!hitCount?.value; - if (!!hitCount?.value) { - hitCount.value *= 2; + if (hitCount?.value) { + hitCount.value += 1; } - if (!!multiplier?.value && pokemon.turnData.hitsLeft % 2 === 1 && pokemon.turnData.hitsLeft !== pokemon.turnData.hitCount) { - multiplier.value *= this.damageMultiplier; + if (multiplier?.value && pokemon.turnData.hitsLeft === 1) { + multiplier.value = this.damageMultiplier; } return true; } @@ -2460,12 +2428,15 @@ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { } } +/** + * Used by Imposter + */ export class PostSummonTransformAbAttr extends PostSummonAbAttr { constructor() { super(true); } - async applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): Promise { + async applyPostSummon(pokemon: Pokemon, _passive: boolean, simulated: boolean, _args: any[]): Promise { const targets = pokemon.getOpponents(); if (simulated || !targets.length) { return simulated; @@ -2474,17 +2445,31 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { let target: Pokemon; if (targets.length > 1) { - pokemon.scene.executeWithSeedOffset(() => target = Utils.randSeedItem(targets), pokemon.scene.currentBattle.waveIndex); + pokemon.scene.executeWithSeedOffset(() => { + // in a double battle, if one of the opposing pokemon is fused the other one will be chosen + // if both are fused, then Imposter will fail below + if (targets[0].fusionSpecies) { + target = targets[1]; + return; + } else if (targets[1].fusionSpecies) { + target = targets[0]; + return; + } + target = Utils.randSeedItem(targets); + }, pokemon.scene.currentBattle.waveIndex); } else { target = targets[0]; } - target = target!; + + // transforming from or into fusion pokemon causes various problems (including crashes and save corruption) + if (target.fusionSpecies || pokemon.fusionSpecies) { + return false; + } + pokemon.summonData.speciesForm = target.getSpeciesForm(); - pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.gender = target.getGender(); - pokemon.summonData.fusionGender = target.getFusionGender(); // Copy all stats (except HP) for (const s of EFFECTIVE_STATS) { @@ -2591,6 +2576,42 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { } } +/** + * Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander}. + * When the source of an ability with this attribute detects a Dondozo as their active ally, the source "jumps + * into the Dondozo's mouth," sharply boosting the Dondozo's stats, cancelling the source's moves, and + * causing attacks that target the source to always miss. + */ +export class CommanderAbAttr extends AbAttr { + constructor() { + super(true); + } + + override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { + // TODO: Should this work with X + Dondozo fusions? + if (pokemon.scene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO) { + // If the ally Dondozo is fainted or was previously "commanded" by + // another Pokemon, this effect cannot apply. + if (pokemon.getAlly().isFainted() || pokemon.getAlly().getTag(BattlerTagType.COMMANDED)) { + return false; + } + + if (!simulated) { + // Lapse the source's semi-invulnerable tags (to avoid visual inconsistencies) + pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + // Play an animation of the source jumping into the ally Dondozo's mouth + pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_APPLY); + // Apply boosts from this effect to the ally Dondozo + pokemon.getAlly().addTag(BattlerTagType.COMMANDED, 0, Moves.NONE, pokemon.id); + // Cancel the source Pokemon's next move (if a move is queued) + pokemon.scene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); + } + return true; + } + return false; + } +} + export class PreSwitchOutAbAttr extends AbAttr { constructor() { super(true); @@ -3159,7 +3180,7 @@ function getAnticipationCondition(): AbAttrCondition { continue; } // the move's base type (not accounting for variable type changes) is super effective - if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) { + if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2) { return true; } // move is a OHKO @@ -3699,16 +3720,16 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { /** * Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1) - * @param {Pokemon} pokemon Pokemon that has this ability - * @param {boolean} passive N/A - * @param {boolean} simulated true if applying in a simulated call. - * @param {any[]} args N/A - * @returns {boolean} true if any opponents are sleeping + * @param pokemon Pokemon that has this ability + * @param passive N/A + * @param simulated `true` if applying in a simulated call. + * @param args N/A + * @returns `true` if any opponents are sleeping */ applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { let hadEffect: boolean = false; for (const opp of pokemon.getOpponents()) { - if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER); pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); @@ -4091,12 +4112,16 @@ export class PostBattleAbAttr extends AbAttr { } export class PostBattleLootAbAttr extends PostBattleAbAttr { + /** + * @param args - `[0]`: boolean for if the battle ended in a victory + * @returns `true` if successful + */ applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; - if (!simulated && postBattleLoot.length) { + if (!simulated && postBattleLoot.length && args[0]) { const randItem = Utils.randSeedItem(postBattleLoot); //@ts-ignore - TODO see below - if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true)) { // TODO: fix. This is a promise!? + if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!? postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1); pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); return true; @@ -4554,14 +4579,15 @@ export class MoneyAbAttr extends PostBattleAbAttr { /** * @param pokemon {@linkcode Pokemon} that is the user of this ability. * @param passive N/A - * @param args N/A - * @returns true + * @param args - `[0]`: boolean for if the battle ended in a victory + * @returns `true` if successful */ applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!simulated) { + if (!simulated && args[0]) { pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2); + return true; } - return true; + return false; } } @@ -4569,13 +4595,12 @@ export class MoneyAbAttr extends PostBattleAbAttr { * Applies a stat change after a Pokémon is summoned, * conditioned on the presence of a specific arena tag. * - * @extends {PostSummonStatStageChangeAbAttr} + * @extends PostSummonStatStageChangeAbAttr */ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageChangeAbAttr { /** * The type of arena tag that conditions the stat change. * @private - * @type {ArenaTagType} */ private tagType: ArenaTagType; @@ -4930,9 +4955,10 @@ class ForceSwitchOutHelper { } /** * For wild Pokémon battles, the Pokémon will flee if the conditions are met (waveIndex and double battles). + * It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss */ } else { - if (!pokemon.scene.currentBattle.waveIndex && pokemon.scene.currentBattle.waveIndex % 10 === 0) { + if (!pokemon.scene.currentBattle.waveIndex || pokemon.scene.currentBattle.waveIndex % 10 === 0) { return false; } @@ -4950,7 +4976,7 @@ class ForceSwitchOutHelper { pokemon.scene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene)); + pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene, false)); pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene)); } } @@ -5392,8 +5418,7 @@ export function initAbilities() { .attr(EffectSporeAbAttr), new Ability(Abilities.SYNCHRONIZE, 3) .attr(SyncEncounterNatureAbAttr) - .attr(SynchronizeStatusAbAttr) - .partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now + .attr(SynchronizeStatusAbAttr), new Ability(Abilities.CLEAR_BODY, 3) .attr(ProtectStatAbAttr) .ignorable(), @@ -5561,7 +5586,9 @@ export function initAbilities() { new Ability(Abilities.ANGER_POINT, 4) .attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6), new Ability(Abilities.UNBURDEN, 4) - .attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN), + .attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN) + .bypassFaint() // Allows reviver seed to activate Unburden + .edgeCase(), // Should not restore Unburden boost if Pokemon loses then regains Unburden ability new Ability(Abilities.HEATPROOF, 4) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(ReduceBurnDamageAbAttr, 0.5) @@ -5686,9 +5713,7 @@ export function initAbilities() { .condition(getSheerForceHitDisableAbCondition()), new Ability(Abilities.SHEER_FORCE, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096) - .attr(MoveEffectChanceMultiplierAbAttr, 0) - .edgeCase() // Should disable shell bell and Meloetta's relic song transformation - .edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented + .attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented new Ability(Abilities.CONTRARY, 5) .attr(StatStageChangeMultiplierAbAttr, -1) .ignorable(), @@ -5756,9 +5781,10 @@ export function initAbilities() { .attr(WonderSkinAbAttr) .ignorable(), new Ability(Abilities.ANALYTIC, 5) - .attr(MovePowerBoostAbAttr, (user, target, move) => - !!target?.getLastXMoves(1).find(m => m.turn === target?.scene.currentBattle.turn) - || user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command !== Command.FIGHT, 1.3), + .attr(MovePowerBoostAbAttr, (user, target, move) => { + const movePhase = user?.scene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user.id); + return Utils.isNullOrUndefined(movePhase); + }, 1.3), new Ability(Abilities.ILLUSION, 5) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) @@ -5906,10 +5932,10 @@ export function initAbilities() { .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1), new Ability(Abilities.WIMP_OUT, 7) .attr(PostDamageForceSwitchAbAttr) - .edgeCase(), // Should not trigger when hurting itself in confusion + .edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode new Ability(Abilities.EMERGENCY_EXIT, 7) .attr(PostDamageForceSwitchAbAttr) - .edgeCase(), // Should not trigger when hurting itself in confusion + .edgeCase(), // Should not trigger when hurting itself in confusion, causes Fake Out to fail turn 1 and succeed turn 2 if pokemon is switched out before battle start via playing in Switch Mode new Ability(Abilities.WATER_COMPACTION, 7) .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2), new Ability(Abilities.MERCILESS, 7) @@ -5925,7 +5951,7 @@ export function initAbilities() { .bypassFaint() .partial(), // Meteor form should protect against status effects and yawn new Ability(Abilities.STAKEOUT, 7) - .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2), + .attr(MovePowerBoostAbAttr, (user, target, move) => !!target?.turnData.switchedInThisTurn, 2), new Ability(Abilities.WATER_BUBBLE, 7) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.FIRE, 0.5) .attr(MoveTypePowerBoostAbAttr, Type.WATER, 2) @@ -5995,7 +6021,7 @@ export function initAbilities() { .bypassFaint(), new Ability(Abilities.CORROSION, 7) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) - .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) + .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented) + fling with toxic orb (not implemented yet) new Ability(Abilities.COMATOSE, 7) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) @@ -6068,11 +6094,9 @@ export function initAbilities() { new Ability(Abilities.NEUROFORCE, 7) .attr(MovePowerBoostAbAttr, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) >= 2, 1.25), new Ability(Abilities.INTREPID_SWORD, 8) - .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true) - .condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)), + .attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true), new Ability(Abilities.DAUNTLESS_SHIELD, 8) - .attr(PostSummonStatStageChangeAbAttr, [ Stat.DEF ], 1, true) - .condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)), + .attr(PostSummonStatStageChangeAbAttr, [ Stat.DEF ], 1, true), new Ability(Abilities.LIBERO, 8) .attr(PokemonTypeChangeAbAttr), //.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.LIBERO)), //Gen 9 Implementation @@ -6242,9 +6266,11 @@ export function initAbilities() { .attr(PreSwitchOutFormChangeAbAttr, (pokemon) => !pokemon.isFainted() ? 1 : pokemon.formIndex) .bypassFaint(), new Ability(Abilities.COMMANDER, 9) + .attr(CommanderAbAttr) + .attr(DoubleBattleChanceAbAttr) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .unimplemented(), + .edgeCase(), // Encore, Frenzy, and other non-`TURN_END` tags don't lapse correctly on the commanding Pokemon. new Ability(Abilities.ELECTROMORPHOSIS, 9) .attr(PostDefendApplyBattlerTagAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattlerTagType.CHARGED), new Ability(Abilities.PROTOSYNTHESIS, 9) @@ -6317,8 +6343,7 @@ export function initAbilities() { .attr(IgnoreOpponentStatStagesAbAttr, [ Stat.EVA ]) .ignorable(), new Ability(Abilities.SUPERSWEET_SYRUP, 9) - .attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1) - .condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)), + .attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1), new Ability(Abilities.HOSPITALITY, 9) .attr(PostSummonAllyHealAbAttr, 4, true), new Ability(Abilities.TOXIC_CHAIN, 9) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 3dd526676a0..8bb74d29a4e 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,13 +1,13 @@ import { Arena } from "#app/field/arena"; import BattleScene from "#app/battle-scene"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; -import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; +import { MoveCategory, allMoves, MoveTarget } from "#app/data/move"; import { getPokemonNameWithAffix } from "#app/messages"; import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import { BattlerIndex } from "#app/battle"; -import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; +import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; @@ -318,17 +318,15 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const priority = new NumberHolder(move.priority); const effectPhase = arena.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { const attacker = effectPhase.getUserPokemon(); - applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, priority); if (attacker) { - applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, false, move, priority); + return move.getPriority(attacker) > 0; } } - return priority.value > 0; + return move.priority > 0; }; /** @@ -780,13 +778,14 @@ class ToxicSpikesTag extends ArenaTrapTag { * Delays the attack's effect by a set amount of turns, usually 3 (including the turn the move is used), * and deals damage after the turn count is reached. */ -class DelayedAttackTag extends ArenaTag { +export class DelayedAttackTag extends ArenaTag { public targetIndex: BattlerIndex; - constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) { - super(tagType, 3, sourceMove, sourceId); + constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH) { + super(tagType, 3, sourceMove, sourceId, side); this.targetIndex = targetIndex; + this.side = side; } lapse(arena: Arena): boolean { @@ -1145,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag { ? arena.scene.getPlayerField() : arena.scene.getEnemyField(); - field.filter(pokemon => !pokemon.isOfType(Type.FIRE)).forEach(pokemon => { + field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => { // "{pokemonNameWithAffix} was hurt by the sea of fire!" pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // TODO: Replace this with a proper animation @@ -1250,7 +1249,7 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove return new ToxicSpikesTag(sourceId, side); case ArenaTagType.FUTURE_SIGHT: case ArenaTagType.DOOM_DESIRE: - return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang + return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!, side); // TODO:questionable bang case ArenaTagType.WISH: return new WishTag(turnCount, sourceId, side); case ArenaTagType.STEALTH_ROCK: diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 2ce693c360b..0f4926cf7c7 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -1,4 +1,4 @@ -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import * as Utils from "#app/utils"; import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; @@ -7666,7 +7666,7 @@ export function initBiomes() { if (biome === Biome.END) { const biomeList = Object.keys(Biome).filter(key => !isNaN(Number(key))); biomeList.pop(); // Removes Biome.END from the list - const randIndex = Utils.randInt(biomeList.length, 1); // Will never be Biome.TOWN + const randIndex = Utils.randSeedInt(biomeList.length, 1); // Will never be Biome.TOWN biome = Biome[biomeList[randIndex]]; } const linkedBiomes: (Biome | [ Biome, integer ])[] = Array.isArray(biomeLinks[biome]) diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 8df92e179d9..4855379f675 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -7,16 +7,16 @@ import { Species } from "#enums/species"; export const speciesEggMoves = { [Species.BULBASAUR]: [ Moves.SAPPY_SEED, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.MATCHA_GOTCHA ], [Species.CHARMANDER]: [ Moves.DRAGON_DANCE, Moves.BITTER_BLADE, Moves.EARTH_POWER, Moves.OBLIVION_WING ], - [Species.SQUIRTLE]: [ Moves.FREEZE_DRY, Moves.SHORE_UP, Moves.BOUNCY_BUBBLE, Moves.ORIGIN_PULSE ], + [Species.SQUIRTLE]: [ Moves.FREEZE_DRY, Moves.ARMOR_CANNON, Moves.BOUNCY_BUBBLE, Moves.ORIGIN_PULSE ], [Species.CATERPIE]: [ Moves.SANDSEAR_STORM, Moves.SILK_TRAP, Moves.TWIN_BEAM, Moves.BLEAKWIND_STORM ], [Species.WEEDLE]: [ Moves.THOUSAND_ARROWS, Moves.NOXIOUS_TORQUE, Moves.ATTACK_ORDER, Moves.VICTORY_DANCE ], [Species.PIDGEY]: [ Moves.WILDBOLT_STORM, Moves.SANDSEAR_STORM, Moves.NASTY_PLOT, Moves.BOOMBURST ], [Species.RATTATA]: [ Moves.HYPER_FANG, Moves.PSYCHIC_FANGS, Moves.FIRE_FANG, Moves.EXTREME_SPEED ], - [Species.SPEAROW]: [ Moves.FLOATY_FALL, Moves.EXTREME_SPEED, Moves.TIDY_UP, Moves.TRIPLE_ARROWS ], + [Species.SPEAROW]: [ Moves.FLOATY_FALL, Moves.HYPER_DRILL, Moves.TIDY_UP, Moves.TRIPLE_ARROWS ], [Species.EKANS]: [ Moves.NOXIOUS_TORQUE, Moves.DRAGON_DANCE, Moves.SLACK_OFF, Moves.SHED_TAIL ], [Species.SANDSHREW]: [ Moves.HIGH_HORSEPOWER, Moves.DIRE_CLAW, Moves.SHORE_UP, Moves.MIGHTY_CLEAVE ], - [Species.NIDORAN_F]: [ Moves.NO_RETREAT, Moves.BANEFUL_BUNKER, Moves.SANDSEAR_STORM, Moves.MALIGNANT_CHAIN ], - [Species.NIDORAN_M]: [ Moves.NOXIOUS_TORQUE, Moves.KINGS_SHIELD, Moves.NO_RETREAT, Moves.PRECIPICE_BLADES ], + [Species.NIDORAN_F]: [ Moves.CALM_MIND, Moves.MOONLIGHT, Moves.MALIGNANT_CHAIN, Moves.SANDSEAR_STORM ], + [Species.NIDORAN_M]: [ Moves.DRAGON_DANCE, Moves.MOUNTAIN_GALE, Moves.NOXIOUS_TORQUE, Moves.PRECIPICE_BLADES ], [Species.VULPIX]: [ Moves.MOONBLAST, Moves.INFERNAL_PARADE, Moves.MORNING_SUN, Moves.TAIL_GLOW ], [Species.ZUBAT]: [ Moves.FLOATY_FALL, Moves.DIRE_CLAW, Moves.SWORDS_DANCE, Moves.COLLISION_COURSE ], [Species.ODDISH]: [ Moves.SLUDGE_BOMB, Moves.FIERY_DANCE, Moves.STRENGTH_SAP, Moves.SPORE ], @@ -31,105 +31,107 @@ export const speciesEggMoves = { [Species.ABRA]: [ Moves.AURA_SPHERE, Moves.BADDY_BAD, Moves.ICE_BEAM, Moves.PSYSTRIKE ], [Species.MACHOP]: [ Moves.COMBAT_TORQUE, Moves.METEOR_MASH, Moves.MOUNTAIN_GALE, Moves.FISSURE ], [Species.BELLSPROUT]: [ Moves.SOLAR_BLADE, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.VICTORY_DANCE ], - [Species.TENTACOOL]: [ Moves.BANEFUL_BUNKER, Moves.STRENGTH_SAP, Moves.BOUNCY_BUBBLE, Moves.MALIGNANT_CHAIN ], + [Species.TENTACOOL]: [ Moves.BANEFUL_BUNKER, Moves.MALIGNANT_CHAIN, Moves.BOUNCY_BUBBLE, Moves.STRENGTH_SAP ], [Species.GEODUDE]: [ Moves.FLARE_BLITZ, Moves.HEAD_SMASH, Moves.SHORE_UP, Moves.SHELL_SMASH ], - [Species.PONYTA]: [ Moves.HIGH_HORSEPOWER, Moves.FIRE_LASH, Moves.SWORDS_DANCE, Moves.VOLT_TACKLE ], - [Species.SLOWPOKE]: [ Moves.BOUNCY_BUBBLE, Moves.FLAMETHROWER, Moves.MYSTICAL_POWER, Moves.SHED_TAIL ], + [Species.PONYTA]: [ Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.SWORDS_DANCE, Moves.VOLT_TACKLE ], + [Species.SLOWPOKE]: [ Moves.BOUNCY_BUBBLE, Moves.FROST_BREATH, Moves.SHED_TAIL, Moves.MYSTICAL_POWER ], [Species.MAGNEMITE]: [ Moves.PARABOLIC_CHARGE, Moves.FLAMETHROWER, Moves.ICE_BEAM, Moves.THUNDERCLAP ], [Species.FARFETCHD]: [ Moves.IVY_CUDGEL, Moves.TRIPLE_ARROWS, Moves.DRILL_RUN, Moves.VICTORY_DANCE ], - [Species.DODUO]: [ Moves.TRIPLE_AXEL, Moves.MULTI_ATTACK, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], + [Species.DODUO]: [ Moves.TRIPLE_AXEL, Moves.HYPER_DRILL, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], [Species.SEEL]: [ Moves.FREEZE_DRY, Moves.BOUNCY_BUBBLE, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ], - [Species.GRIMER]: [ Moves.SUCKER_PUNCH, Moves.CURSE, Moves.STRENGTH_SAP, Moves.NOXIOUS_TORQUE ], + [Species.GRIMER]: [ Moves.SUCKER_PUNCH, Moves.CURSE, Moves.NOXIOUS_TORQUE, Moves.STRENGTH_SAP ], [Species.SHELLDER]: [ Moves.ROCK_BLAST, Moves.WATER_SHURIKEN, Moves.BANEFUL_BUNKER, Moves.BONE_RUSH ], - [Species.GASTLY]: [ Moves.SLUDGE_BOMB, Moves.AURA_SPHERE, Moves.NASTY_PLOT, Moves.ASTRAL_BARRAGE ], + [Species.GASTLY]: [ Moves.MALIGNANT_CHAIN, Moves.AURA_SPHERE, Moves.PARTING_SHOT, Moves.DARK_VOID ], [Species.ONIX]: [ Moves.SHORE_UP, Moves.THOUSAND_WAVES, Moves.COIL, Moves.DIAMOND_STORM ], [Species.DROWZEE]: [ Moves.BADDY_BAD, Moves.STRENGTH_SAP, Moves.LUMINA_CRASH, Moves.DARK_VOID ], - [Species.KRABBY]: [ Moves.DIRE_CLAW, Moves.JET_PUNCH, Moves.IVY_CUDGEL, Moves.SHELL_SMASH ], + [Species.KRABBY]: [ Moves.DIRE_CLAW, Moves.DRAGON_HAMMER, Moves.IVY_CUDGEL, Moves.JET_PUNCH ], [Species.VOLTORB]: [ Moves.NASTY_PLOT, Moves.FUSION_FLARE, Moves.FROST_BREATH, Moves.ELECTRO_DRIFT ], [Species.EXEGGCUTE]: [ Moves.FICKLE_BEAM, Moves.APPLE_ACID, Moves.TRICK_ROOM, Moves.LUMINA_CRASH ], [Species.CUBONE]: [ Moves.HEAD_SMASH, Moves.WOOD_HAMMER, Moves.SHADOW_SNEAK, Moves.BITTER_BLADE ], [Species.LICKITUNG]: [ Moves.CRUSH_GRIP, Moves.FIRE_LASH, Moves.SLACK_OFF, Moves.MAGICAL_TORQUE ], [Species.KOFFING]: [ Moves.SCALD, Moves.RECOVER, Moves.BODY_PRESS, Moves.MALIGNANT_CHAIN ], [Species.RHYHORN]: [ Moves.SHORE_UP, Moves.ICE_HAMMER, Moves.ACCELEROCK, Moves.HEAD_SMASH ], - [Species.TANGELA]: [ Moves.STRENGTH_SAP, Moves.SNAP_TRAP, Moves.PARTING_SHOT, Moves.SAPPY_SEED ], + [Species.TANGELA]: [ Moves.NATURES_MADNESS, Moves.SNAP_TRAP, Moves.PARTING_SHOT, Moves.SAPPY_SEED ], [Species.KANGASKHAN]: [ Moves.POWER_UP_PUNCH, Moves.TRAILBLAZE, Moves.FACADE, Moves.SEISMIC_TOSS ], - [Species.HORSEA]: [ Moves.SNIPE_SHOT, Moves.FROST_BREATH, Moves.HURRICANE, Moves.SPACIAL_REND ], + [Species.HORSEA]: [ Moves.SNIPE_SHOT, Moves.FROST_BREATH, Moves.SLUDGE_BOMB, Moves.CLANGING_SCALES ], [Species.GOLDEEN]: [ Moves.GLACIAL_LANCE, Moves.SUPERCELL_SLAM, Moves.DRAGON_DANCE, Moves.FISHIOUS_REND ], [Species.STARYU]: [ Moves.CALM_MIND, Moves.BOUNCY_BUBBLE, Moves.MOONBLAST, Moves.MYSTICAL_POWER ], - [Species.SCYTHER]: [ Moves.MIGHTY_CLEAVE, Moves.BUG_BITE, Moves.STORM_THROW, Moves.DOUBLE_IRON_BASH ], + [Species.SCYTHER]: [ Moves.MIGHTY_CLEAVE, Moves.GEAR_GRIND, Moves.STORM_THROW, Moves.BITTER_BLADE ], [Species.PINSIR]: [ Moves.HEADLONG_RUSH, Moves.LEECH_LIFE, Moves.CRUSH_GRIP, Moves.EXTREME_SPEED ], - [Species.TAUROS]: [ Moves.HIGH_HORSEPOWER, Moves.FIRE_LASH, Moves.LIQUIDATION, Moves.COMBAT_TORQUE ], + [Species.TAUROS]: [ Moves.SWORDS_DANCE, Moves.FIRE_LASH, Moves.WICKED_TORQUE, Moves.COLLISION_COURSE ], [Species.MAGIKARP]: [ Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.DRAGON_ASCENT, Moves.SURGING_STRIKES ], [Species.LAPRAS]: [ Moves.RECOVER, Moves.FREEZE_DRY, Moves.SCALD, Moves.SHELL_SMASH ], [Species.DITTO]: [ Moves.MIMIC, Moves.SKETCH, Moves.METRONOME, Moves.IMPRISON ], [Species.EEVEE]: [ Moves.WISH, Moves.NO_RETREAT, Moves.ZIPPY_ZAP, Moves.BOOMBURST ], [Species.PORYGON]: [ Moves.THUNDERCLAP, Moves.AURA_SPHERE, Moves.FLAMETHROWER, Moves.TECHNO_BLAST ], - [Species.OMANYTE]: [ Moves.FREEZE_DRY, Moves.EARTH_POWER, Moves.POWER_GEM, Moves.STEAM_ERUPTION ], - [Species.KABUTO]: [ Moves.CEASELESS_EDGE, Moves.HIGH_HORSEPOWER, Moves.TRIPLE_DIVE, Moves.MIGHTY_CLEAVE ], + [Species.OMANYTE]: [ Moves.FREEZE_DRY, Moves.GIGA_DRAIN, Moves.POWER_GEM, Moves.STEAM_ERUPTION ], + [Species.KABUTO]: [ Moves.CEASELESS_EDGE, Moves.HIGH_HORSEPOWER, Moves.CRABHAMMER, Moves.MIGHTY_CLEAVE ], [Species.AERODACTYL]: [ Moves.FLOATY_FALL, Moves.FLARE_BLITZ, Moves.SWORDS_DANCE, Moves.MIGHTY_CLEAVE ], [Species.ARTICUNO]: [ Moves.EARTH_POWER, Moves.CALM_MIND, Moves.AURORA_VEIL, Moves.AEROBLAST ], [Species.ZAPDOS]: [ Moves.BLEAKWIND_STORM, Moves.CALM_MIND, Moves.SANDSEAR_STORM, Moves.ELECTRO_SHOT ], - [Species.MOLTRES]: [ Moves.SCORCHING_SANDS, Moves.CALM_MIND, Moves.AEROBLAST, Moves.TORCH_SONG ], + [Species.MOLTRES]: [ Moves.EARTH_POWER, Moves.CALM_MIND, Moves.AEROBLAST, Moves.TORCH_SONG ], [Species.DRATINI]: [ Moves.DRAGON_HAMMER, Moves.CRUSH_GRIP, Moves.FIRE_LASH, Moves.GIGATON_HAMMER ], [Species.MEWTWO]: [ Moves.METEOR_MASH, Moves.MOONBLAST, Moves.THUNDEROUS_KICK, Moves.PHOTON_GEYSER ], [Species.MEW]: [ Moves.PHOTON_GEYSER, Moves.MOONBLAST, Moves.ASTRAL_BARRAGE, Moves.SHELL_SMASH ], + [Species.CHIKORITA]: [ Moves.SAPPY_SEED, Moves.STONE_AXE, Moves.DRAGON_DANCE, Moves.SPORE ], [Species.CYNDAQUIL]: [ Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.ELECTRO_DRIFT ], - [Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.FISHIOUS_REND ], + [Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.SURGING_STRIKES ], [Species.SENTRET]: [ Moves.TIDY_UP, Moves.FAKE_OUT, Moves.NUZZLE, Moves.EXTREME_SPEED ], [Species.HOOTHOOT]: [ Moves.CALM_MIND, Moves.ESPER_WING, Moves.AEROBLAST, Moves.BOOMBURST ], - [Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.THIEF, Moves.PARTING_SHOT, Moves.SPORE ], + [Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.MAT_BLOCK, Moves.PARTING_SHOT, Moves.SPORE ], [Species.SPINARAK]: [ Moves.PARTING_SHOT, Moves.ATTACK_ORDER, Moves.GASTRO_ACID, Moves.STRENGTH_SAP ], [Species.CHINCHOU]: [ Moves.THUNDERCLAP, Moves.BOUNCY_BUBBLE, Moves.THUNDER_CAGE, Moves.TAIL_GLOW ], [Species.PICHU]: [ Moves.MOONBLAST, Moves.TRIPLE_AXEL, Moves.FIERY_DANCE, Moves.AURA_WHEEL ], [Species.CLEFFA]: [ Moves.CALM_MIND, Moves.EARTH_POWER, Moves.WISH, Moves.LIGHT_OF_RUIN ], [Species.IGGLYBUFF]: [ Moves.DRAIN_PUNCH, Moves.GRAV_APPLE, Moves.SOFT_BOILED, Moves.EXTREME_SPEED ], - [Species.TOGEPI]: [ Moves.SCORCHING_SANDS, Moves.ROOST, Moves.RELIC_SONG, Moves.FIERY_DANCE ], - [Species.NATU]: [ Moves.AEROBLAST, Moves.ROOST, Moves.MOONBLAST, Moves.LUMINA_CRASH ], + [Species.TOGEPI]: [ Moves.SCORCHING_SANDS, Moves.SPLISHY_SPLASH, Moves.RELIC_SONG, Moves.FIERY_DANCE ], + [Species.NATU]: [ Moves.REVIVAL_BLESSING, Moves.NASTY_PLOT, Moves.MOONBLAST, Moves.OBLIVION_WING ], [Species.MAREEP]: [ Moves.ICE_BEAM, Moves.PARABOLIC_CHARGE, Moves.CORE_ENFORCER, Moves.TAIL_GLOW ], [Species.HOPPIP]: [ Moves.FLOATY_FALL, Moves.STRENGTH_SAP, Moves.SAPPY_SEED, Moves.SPORE ], - [Species.AIPOM]: [ Moves.TIDY_UP, Moves.STORM_THROW, Moves.FAKE_OUT, Moves.POPULATION_BOMB ], + [Species.AIPOM]: [ Moves.ROCK_BLAST, Moves.STORM_THROW, Moves.FAKE_OUT, Moves.SWORDS_DANCE ], [Species.SUNKERN]: [ Moves.SPORE, Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.HYDRO_STEAM ], [Species.YANMA]: [ Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.HEAT_WAVE, Moves.BLEAKWIND_STORM ], - [Species.WOOPER]: [ Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.CURSE, Moves.SURGING_STRIKES ], + [Species.WOOPER]: [ Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.SHED_TAIL, Moves.SURGING_STRIKES ], [Species.MURKROW]: [ Moves.TRIPLE_ARROWS, Moves.FLOATY_FALL, Moves.TIDY_UP, Moves.WICKED_BLOW ], - [Species.MISDREAVUS]: [ Moves.TAKE_HEART, Moves.MOONBLAST, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ], + [Species.MISDREAVUS]: [ Moves.TAKE_HEART, Moves.MOONBLAST, Moves.AURA_SPHERE, Moves.MOONGEIST_BEAM ], [Species.UNOWN]: [ Moves.NATURE_POWER, Moves.COSMIC_POWER, Moves.ANCIENT_POWER, Moves.MYSTICAL_POWER ], [Species.GIRAFARIG]: [ Moves.MYSTICAL_POWER, Moves.NIGHT_DAZE, Moves.RECOVER, Moves.BOOMBURST ], [Species.PINECO]: [ Moves.METAL_BURST, Moves.SHORE_UP, Moves.BODY_PRESS, Moves.DIAMOND_STORM ], [Species.DUNSPARCE]: [ Moves.WICKED_TORQUE, Moves.MAGICAL_TORQUE, Moves.BLAZING_TORQUE, Moves.EXTREME_SPEED ], - [Species.GLIGAR]: [ Moves.FLOATY_FALL, Moves.THOUSAND_WAVES, Moves.ROOST, Moves.MIGHTY_CLEAVE ], - [Species.SNUBBULL]: [ Moves.FACADE, Moves.EARTHQUAKE, Moves.SWORDS_DANCE, Moves.EXTREME_SPEED ], - [Species.QWILFISH]: [ Moves.BARB_BARRAGE, Moves.BANEFUL_BUNKER, Moves.KNOCK_OFF, Moves.FISHIOUS_REND ], + [Species.GLIGAR]: [ Moves.FLOATY_FALL, Moves.THOUSAND_WAVES, Moves.SPIKY_SHIELD, Moves.MIGHTY_CLEAVE ], + [Species.SNUBBULL]: [ Moves.FACADE, Moves.HIGH_HORSEPOWER, Moves.SWORDS_DANCE, Moves.EXTREME_SPEED ], + [Species.QWILFISH]: [ Moves.BARB_BARRAGE, Moves.BANEFUL_BUNKER, Moves.RECOVER, Moves.FISHIOUS_REND ], [Species.SHUCKLE]: [ Moves.STUFF_CHEEKS, Moves.HEAL_ORDER, Moves.BODY_PRESS, Moves.SALT_CURE ], - [Species.HERACROSS]: [ Moves.ROCK_BLAST, Moves.FIRST_IMPRESSION, Moves.ICICLE_SPEAR, Moves.DRAGON_DANCE ], - [Species.SNEASEL]: [ Moves.DIRE_CLAW, Moves.STORM_THROW, Moves.TRIPLE_AXEL, Moves.WICKED_BLOW ], + [Species.HERACROSS]: [ Moves.ROCK_BLAST, Moves.FIRST_IMPRESSION, Moves.ICICLE_SPEAR, Moves.TIDY_UP ], + [Species.SNEASEL]: [ Moves.DIRE_CLAW, Moves.DARKEST_LARIAT, Moves.TRIPLE_AXEL, Moves.CLOSE_COMBAT ], [Species.TEDDIURSA]: [ Moves.MOUNTAIN_GALE, Moves.FAKE_OUT, Moves.SLACK_OFF, Moves.PRECIPICE_BLADES ], [Species.SLUGMA]: [ Moves.BURNING_BULWARK, Moves.POWER_GEM, Moves.SOLAR_BEAM, Moves.MAGMA_STORM ], - [Species.SWINUB]: [ Moves.SLACK_OFF, Moves.LANDS_WRATH, Moves.MIGHTY_CLEAVE, Moves.GLACIAL_LANCE ], + [Species.SWINUB]: [ Moves.SLACK_OFF, Moves.MOUNTAIN_GALE, Moves.STONE_AXE, Moves.PRECIPICE_BLADES ], [Species.CORSOLA]: [ Moves.SCALD, Moves.FREEZE_DRY, Moves.STRENGTH_SAP, Moves.SALT_CURE ], [Species.REMORAID]: [ Moves.WATER_SHURIKEN, Moves.TAKE_HEART, Moves.SHELL_SIDE_ARM, Moves.BOUNCY_BUBBLE ], [Species.DELIBIRD]: [ Moves.BONEMERANG, Moves.FLOATY_FALL, Moves.VICTORY_DANCE, Moves.GLACIAL_LANCE ], [Species.SKARMORY]: [ Moves.ROOST, Moves.BODY_PRESS, Moves.SPIKY_SHIELD, Moves.BEAK_BLAST ], - [Species.HOUNDOUR]: [ Moves.MOONLIGHT, Moves.FIERY_WRATH, Moves.SECRET_SWORD, Moves.HYDRO_STEAM ], - [Species.PHANPY]: [ Moves.SHORE_UP, Moves.SWORDS_DANCE, Moves.ICICLE_CRASH, Moves.COLLISION_COURSE ], + [Species.HOUNDOUR]: [ Moves.EARTH_POWER, Moves.THUNDERBOLT, Moves.MOONBLAST, Moves.FIERY_WRATH ], + [Species.PHANPY]: [ Moves.SHORE_UP, Moves.SWORDS_DANCE, Moves.MOUNTAIN_GALE, Moves.COLLISION_COURSE ], [Species.STANTLER]: [ Moves.THUNDEROUS_KICK, Moves.PHOTON_GEYSER, Moves.SWORDS_DANCE, Moves.BOOMBURST ], [Species.SMEARGLE]: [ Moves.CONVERSION, Moves.BURNING_BULWARK, Moves.SALT_CURE, Moves.DARK_VOID ], [Species.TYROGUE]: [ Moves.VICTORY_DANCE, Moves.THUNDEROUS_KICK, Moves.METEOR_MASH, Moves.WICKED_BLOW ], - [Species.SMOOCHUM]: [ Moves.EXPANDING_FORCE, Moves.AURA_SPHERE, Moves.FREEZE_DRY, Moves.QUIVER_DANCE ], + [Species.SMOOCHUM]: [ Moves.LUSTER_PURGE, Moves.AURA_SPHERE, Moves.FREEZE_DRY, Moves.QUIVER_DANCE ], [Species.ELEKID]: [ Moves.FIRE_LASH, Moves.ZING_ZAP, Moves.MOUNTAIN_GALE, Moves.SHIFT_GEAR ], - [Species.MAGBY]: [ Moves.THUNDERCLAP, Moves.EARTH_POWER, Moves.ARMOR_CANNON, Moves.FLEUR_CANNON ], - [Species.MILTANK]: [ Moves.BODY_PRESS, Moves.BULK_UP, Moves.YAWN, Moves.SIZZLY_SLIDE ], + [Species.MAGBY]: [ Moves.THUNDERCLAP, Moves.EARTH_POWER, Moves.ENERGY_BALL, Moves.BLUE_FLARE ], + [Species.MILTANK]: [ Moves.BODY_PRESS, Moves.BULK_UP, Moves.KNOCK_OFF, Moves.SIZZLY_SLIDE ], [Species.RAIKOU]: [ Moves.PARABOLIC_CHARGE, Moves.NASTY_PLOT, Moves.FROST_BREATH, Moves.ELECTRO_DRIFT ], - [Species.ENTEI]: [ Moves.BURNING_BULWARK, Moves.DRAGON_DANCE, Moves.EARTHQUAKE, Moves.MIGHTY_CLEAVE ], + [Species.ENTEI]: [ Moves.BURNING_BULWARK, Moves.DRAGON_DANCE, Moves.EARTHQUAKE, Moves.PYRO_BALL ], [Species.SUICUNE]: [ Moves.RECOVER, Moves.NASTY_PLOT, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.LARVITAR]: [ Moves.DRAGON_DANCE, Moves.MOUNTAIN_GALE, Moves.SHORE_UP, Moves.DIAMOND_STORM ], [Species.LUGIA]: [ Moves.NASTY_PLOT, Moves.LUMINA_CRASH, Moves.AURA_SPHERE, Moves.OBLIVION_WING ], [Species.HO_OH]: [ Moves.BRAVE_BIRD, Moves.DRAGON_DANCE, Moves.REVIVAL_BLESSING, Moves.BOLT_BEAK ], [Species.CELEBI]: [ Moves.PHOTON_GEYSER, Moves.MATCHA_GOTCHA, Moves.REVIVAL_BLESSING, Moves.QUIVER_DANCE ], - [Species.TREECKO]: [ Moves.NASTY_PLOT, Moves.APPLE_ACID, Moves.SECRET_SWORD, Moves.DRAGON_ENERGY ], - [Species.TORCHIC]: [ Moves.HIGH_JUMP_KICK, Moves.SUPERCELL_SLAM, Moves.BURNING_BULWARK, Moves.V_CREATE ], - [Species.MUDKIP]: [ Moves.SHORE_UP, Moves.MOUNTAIN_GALE, Moves.BULK_UP, Moves.SURGING_STRIKES ], + + [Species.TREECKO]: [ Moves.NASTY_PLOT, Moves.CORE_ENFORCER, Moves.FLAMETHROWER, Moves.SEED_FLARE ], + [Species.TORCHIC]: [ Moves.THUNDEROUS_KICK, Moves.ZING_ZAP, Moves.BURNING_BULWARK, Moves.PYRO_BALL ], + [Species.MUDKIP]: [ Moves.SHORE_UP, Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.PRECIPICE_BLADES ], [Species.POOCHYENA]: [ Moves.JAW_LOCK, Moves.CLOSE_COMBAT, Moves.DIRE_CLAW, Moves.NO_RETREAT ], [Species.ZIGZAGOON]: [ Moves.EXTREME_SPEED, Moves.NUZZLE, Moves.HIGH_HORSEPOWER, Moves.TIDY_UP ], [Species.WURMPLE]: [ Moves.BATON_PASS, Moves.BLEAKWIND_STORM, Moves.STORED_POWER, Moves.MALIGNANT_CHAIN ], @@ -151,35 +153,35 @@ export const speciesEggMoves = { [Species.MAWILE]: [ Moves.BULLET_PUNCH, Moves.MAGICAL_TORQUE, Moves.EARTHQUAKE, Moves.SHIFT_GEAR ], [Species.ARON]: [ Moves.HEAD_SMASH, Moves.BODY_PRESS, Moves.SHORE_UP, Moves.SALT_CURE ], [Species.MEDITITE]: [ Moves.THUNDEROUS_KICK, Moves.SUCKER_PUNCH, Moves.BULLET_PUNCH, Moves.PHOTON_GEYSER ], - [Species.ELECTRIKE]: [ Moves.RISING_VOLTAGE, Moves.FLAMETHROWER, Moves.NASTY_PLOT, Moves.ICE_BEAM ], + [Species.ELECTRIKE]: [ Moves.FROST_BREATH, Moves.HEAT_WAVE, Moves.NASTY_PLOT, Moves.ELECTRO_DRIFT ], [Species.PLUSLE]: [ Moves.FLAMETHROWER, Moves.GLITZY_GLOW, Moves.SPLISHY_SPLASH, Moves.TAIL_GLOW ], [Species.MINUN]: [ Moves.ICE_BEAM, Moves.BADDY_BAD, Moves.SPARKLY_SWIRL, Moves.TAIL_GLOW ], [Species.VOLBEAT]: [ Moves.BATON_PASS, Moves.STICKY_WEB, Moves.DECORATE, Moves.VICTORY_DANCE ], [Species.ILLUMISE]: [ Moves.PARTING_SHOT, Moves.GLITZY_GLOW, Moves.POWDER, Moves.QUIVER_DANCE ], - [Species.GULPIN]: [ Moves.STRENGTH_SAP, Moves.EARTH_POWER, Moves.CALM_MIND, Moves.MALIGNANT_CHAIN ], + [Species.GULPIN]: [ Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.CALM_MIND, Moves.STRENGTH_SAP ], [Species.CARVANHA]: [ Moves.THUNDER_FANG, Moves.SWORDS_DANCE, Moves.OBSTRUCT, Moves.SURGING_STRIKES ], - [Species.WAILMER]: [ Moves.TAKE_HEART, Moves.BOUNCY_BUBBLE, Moves.SLACK_OFF, Moves.COMEUPPANCE ], - [Species.NUMEL]: [ Moves.TRICK_ROOM, Moves.ENERGY_BALL, Moves.MORNING_SUN, Moves.BLUE_FLARE ], + [Species.WAILMER]: [ Moves.TAKE_HEART, Moves.COMEUPPANCE, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ], + [Species.NUMEL]: [ Moves.TRICK_ROOM, Moves.ENERGY_BALL, Moves.SLACK_OFF, Moves.BLUE_FLARE ], [Species.TORKOAL]: [ Moves.MORNING_SUN, Moves.BURNING_BULWARK, Moves.BODY_PRESS, Moves.HYDRO_STEAM ], [Species.SPOINK]: [ Moves.AURA_SPHERE, Moves.MILK_DRINK, Moves.EXPANDING_FORCE, Moves.TAIL_GLOW ], [Species.SPINDA]: [ Moves.SUPERPOWER, Moves.SLACK_OFF, Moves.FLEUR_CANNON, Moves.V_CREATE ], [Species.TRAPINCH]: [ Moves.FIRE_LASH, Moves.DRAGON_DARTS, Moves.THOUSAND_ARROWS, Moves.DRAGON_ENERGY ], - [Species.CACNEA]: [ Moves.EARTH_POWER, Moves.CEASELESS_EDGE, Moves.NIGHT_DAZE, Moves.IVY_CUDGEL ], + [Species.CACNEA]: [ Moves.EARTH_POWER, Moves.CEASELESS_EDGE, Moves.NIGHT_DAZE, Moves.SAPPY_SEED ], [Species.SWABLU]: [ Moves.ROOST, Moves.NASTY_PLOT, Moves.FLOATY_FALL, Moves.BOOMBURST ], [Species.ZANGOOSE]: [ Moves.FACADE, Moves.HIGH_HORSEPOWER, Moves.EXTREME_SPEED, Moves.TIDY_UP ], [Species.SEVIPER]: [ Moves.ICE_BEAM, Moves.BITTER_BLADE, Moves.SUCKER_PUNCH, Moves.NO_RETREAT ], - [Species.LUNATONE]: [ Moves.POWER_GEM, Moves.MOONGEIST_BEAM, Moves.SHELL_SMASH, Moves.LUMINA_CRASH ], - [Species.SOLROCK]: [ Moves.PSYSHIELD_BASH, Moves.MIGHTY_CLEAVE, Moves.SHELL_SMASH, Moves.SACRED_FIRE ], + [Species.LUNATONE]: [ Moves.REVELATION_DANCE, Moves.MOONGEIST_BEAM, Moves.SHELL_SMASH, Moves.LUMINA_CRASH ], + [Species.SOLROCK]: [ Moves.MIGHTY_CLEAVE, Moves.PHOTON_GEYSER, Moves.SHELL_SMASH, Moves.SACRED_FIRE ], [Species.BARBOACH]: [ Moves.DRAGON_DANCE, Moves.ZING_ZAP, Moves.ICE_SPINNER, Moves.SURGING_STRIKES ], - [Species.CORPHISH]: [ Moves.CEASELESS_EDGE, Moves.JET_PUNCH, Moves.SUCKER_PUNCH, Moves.SHELL_SMASH ], - [Species.BALTOY]: [ Moves.RECOVER, Moves.STORED_POWER, Moves.BODY_PRESS, Moves.MYSTICAL_POWER ], + [Species.CORPHISH]: [ Moves.CEASELESS_EDGE, Moves.SHELL_SIDE_ARM, Moves.SUCKER_PUNCH, Moves.JET_PUNCH ], + [Species.BALTOY]: [ Moves.RECOVER, Moves.GLARE, Moves.RUINATION, Moves.MYSTICAL_POWER ], [Species.LILEEP]: [ Moves.POWER_GEM, Moves.SCALD, Moves.STRENGTH_SAP, Moves.SAPPY_SEED ], [Species.ANORITH]: [ Moves.FIRST_IMPRESSION, Moves.LEECH_LIFE, Moves.DRAGON_DANCE, Moves.MIGHTY_CLEAVE ], [Species.FEEBAS]: [ Moves.CALM_MIND, Moves.FREEZE_DRY, Moves.MOONBLAST, Moves.STEAM_ERUPTION ], [Species.CASTFORM]: [ Moves.BOOMBURST, Moves.HYDRO_STEAM, Moves.ERUPTION, Moves.QUIVER_DANCE ], [Species.KECLEON]: [ Moves.ZIPPY_ZAP, Moves.COIL, Moves.EXTREME_SPEED, Moves.MULTI_ATTACK ], [Species.SHUPPET]: [ Moves.STORM_THROW, Moves.TIDY_UP, Moves.PARTING_SHOT, Moves.SPECTRAL_THIEF ], - [Species.DUSKULL]: [ Moves.BULK_UP, Moves.DRAIN_PUNCH, Moves.STRENGTH_SAP, Moves.RAGE_FIST ], + [Species.DUSKULL]: [ Moves.BULK_UP, Moves.DRAIN_PUNCH, Moves.RECOVER, Moves.RAGE_FIST ], [Species.TROPIUS]: [ Moves.STUFF_CHEEKS, Moves.EARTH_POWER, Moves.APPLE_ACID, Moves.SAPPY_SEED ], [Species.ABSOL]: [ Moves.KOWTOW_CLEAVE, Moves.SACRED_SWORD, Moves.PSYBLADE, Moves.BITTER_BLADE ], [Species.WYNAUT]: [ Moves.RECOVER, Moves.SHED_TAIL, Moves.TAUNT, Moves.COMEUPPANCE ], @@ -188,21 +190,22 @@ export const speciesEggMoves = { [Species.CLAMPERL]: [ Moves.SHELL_SIDE_ARM, Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.RELICANTH]: [ Moves.DRAGON_DANCE, Moves.SHORE_UP, Moves.WAVE_CRASH, Moves.DIAMOND_STORM ], [Species.LUVDISC]: [ Moves.BATON_PASS, Moves.HEART_SWAP, Moves.GLITZY_GLOW, Moves.REVIVAL_BLESSING ], - [Species.BAGON]: [ Moves.FLOATY_FALL, Moves.FIRE_LASH, Moves.DRAGON_DANCE, Moves.GLAIVE_RUSH ], + [Species.BAGON]: [ Moves.FLOATY_FALL, Moves.FIRE_LASH, Moves.DRAGON_DANCE, Moves.DRAGON_DARTS ], [Species.BELDUM]: [ Moves.HEADLONG_RUSH, Moves.DRAIN_PUNCH, Moves.TRIPLE_AXEL, Moves.SHIFT_GEAR ], [Species.REGIROCK]: [ Moves.STONE_AXE, Moves.BODY_PRESS, Moves.SHORE_UP, Moves.SALT_CURE ], [Species.REGICE]: [ Moves.EARTH_POWER, Moves.TAKE_HEART, Moves.RECOVER, Moves.FREEZE_DRY ], [Species.REGISTEEL]: [ Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.RECOVER, Moves.GIGATON_HAMMER ], [Species.LATIAS]: [ Moves.CORE_ENFORCER, Moves.FUSION_FLARE, Moves.SPARKLY_SWIRL, Moves.MYSTICAL_POWER ], [Species.LATIOS]: [ Moves.CORE_ENFORCER, Moves.BLUE_FLARE, Moves.NASTY_PLOT, Moves.TACHYON_CUTTER ], - [Species.KYOGRE]: [ Moves.RECOVER, Moves.HURRICANE, Moves.FREEZY_FROST, Moves.WILDBOLT_STORM ], + [Species.KYOGRE]: [ Moves.WILDBOLT_STORM, Moves.HURRICANE, Moves.FREEZY_FROST, Moves.BOUNCY_BUBBLE ], [Species.GROUDON]: [ Moves.STONE_AXE, Moves.SOLAR_BLADE, Moves.MORNING_SUN, Moves.SACRED_FIRE ], [Species.RAYQUAZA]: [ Moves.V_CREATE, Moves.DRAGON_DARTS, Moves.CORE_ENFORCER, Moves.OBLIVION_WING ], [Species.JIRACHI]: [ Moves.TACHYON_CUTTER, Moves.TRIPLE_ARROWS, Moves.ROCK_SLIDE, Moves.SHELL_SMASH ], - [Species.DEOXYS]: [ Moves.COLLISION_COURSE, Moves.EARTH_POWER, Moves.PARTING_SHOT, Moves.LUMINA_CRASH ], + [Species.DEOXYS]: [ Moves.COLLISION_COURSE, Moves.FUSION_FLARE, Moves.PARTING_SHOT, Moves.LUMINA_CRASH ], + [Species.TURTWIG]: [ Moves.SHELL_SMASH, Moves.MIGHTY_CLEAVE, Moves.ICE_SPINNER, Moves.SAPPY_SEED ], [Species.CHIMCHAR]: [ Moves.FIERY_DANCE, Moves.SECRET_SWORD, Moves.TRIPLE_AXEL, Moves.SACRED_FIRE ], - [Species.PIPLUP]: [ Moves.KINGS_SHIELD, Moves.TACHYON_CUTTER, Moves.ROOST, Moves.STEAM_ERUPTION ], + [Species.PIPLUP]: [ Moves.KINGS_SHIELD, Moves.TACHYON_CUTTER, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.STARLY]: [ Moves.SWORDS_DANCE, Moves.HEAD_CHARGE, Moves.FLARE_BLITZ, Moves.EXTREME_SPEED ], [Species.BIDOOF]: [ Moves.EXTREME_SPEED, Moves.COSMIC_POWER, Moves.POWER_TRIP, Moves.AQUA_STEP ], [Species.KRICKETOT]: [ Moves.BONEMERANG, Moves.VICTORY_DANCE, Moves.STONE_AXE, Moves.POPULATION_BOMB ], @@ -214,7 +217,7 @@ export const speciesEggMoves = { [Species.COMBEE]: [ Moves.SPORE, Moves.FLOATY_FALL, Moves.KINGS_SHIELD, Moves.VICTORY_DANCE ], [Species.PACHIRISU]: [ Moves.FREEZY_FROST, Moves.SIZZLY_SLIDE, Moves.SLACK_OFF, Moves.ZIPPY_ZAP ], [Species.BUIZEL]: [ Moves.JET_PUNCH, Moves.TRIPLE_AXEL, Moves.SUPERCELL_SLAM, Moves.SURGING_STRIKES ], - [Species.CHERUBI]: [ Moves.SPORE, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.FLOWER_TRICK ], + [Species.CHERUBI]: [ Moves.SLEEP_POWDER, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.FLOWER_TRICK ], [Species.SHELLOS]: [ Moves.BOUNCY_BUBBLE, Moves.SCORCHING_SANDS, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.DRIFLOON]: [ Moves.WILL_O_WISP, Moves.MIND_BLOWN, Moves.CALM_MIND, Moves.OBLIVION_WING ], [Species.BUNEARY]: [ Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.THUNDEROUS_KICK, Moves.MULTI_ATTACK ], @@ -225,36 +228,37 @@ export const speciesEggMoves = { [Species.BONSLY]: [ Moves.ACCELEROCK, Moves.SWORDS_DANCE, Moves.STRENGTH_SAP, Moves.SAPPY_SEED ], [Species.MIME_JR]: [ Moves.CHILLY_RECEPTION, Moves.MOONBLAST, Moves.FROST_BREATH, Moves.LUMINA_CRASH ], [Species.HAPPINY]: [ Moves.COTTON_GUARD, Moves.SEISMIC_TOSS, Moves.SIZZLY_SLIDE, Moves.REVIVAL_BLESSING ], - [Species.CHATOT]: [ Moves.SPARKLING_ARIA, Moves.TORCH_SONG, Moves.BATON_PASS, Moves.BOOMBURST ], + [Species.CHATOT]: [ Moves.SPARKLING_ARIA, Moves.BOOMBURST, Moves.BATON_PASS, Moves.TORCH_SONG ], [Species.SPIRITOMB]: [ Moves.PARTING_SHOT, Moves.BADDY_BAD, Moves.STRENGTH_SAP, Moves.SPECTRAL_THIEF ], - [Species.GIBLE]: [ Moves.DRAGON_DANCE, Moves.BITTER_BLADE, Moves.SHORE_UP, Moves.THOUSAND_ARROWS ], + [Species.GIBLE]: [ Moves.DRAGON_DANCE, Moves.BITTER_BLADE, Moves.DRAGON_HAMMER, Moves.PRECIPICE_BLADES ], [Species.MUNCHLAX]: [ Moves.STUFF_CHEEKS, Moves.GRAV_APPLE, Moves.SLACK_OFF, Moves.EXTREME_SPEED ], - [Species.RIOLU]: [ Moves.THUNDEROUS_KICK, Moves.TACHYON_CUTTER, Moves.TRIPLE_AXEL, Moves.DOUBLE_IRON_BASH ], + [Species.RIOLU]: [ Moves.THUNDEROUS_KICK, Moves.TACHYON_CUTTER, Moves.TRIPLE_AXEL, Moves.SUNSTEEL_STRIKE ], [Species.HIPPOPOTAS]: [ Moves.SHORE_UP, Moves.STONE_AXE, Moves.BULK_UP, Moves.SALT_CURE ], [Species.SKORUPI]: [ Moves.COIL, Moves.DIRE_CLAW, Moves.CRABHAMMER, Moves.WICKED_BLOW ], [Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.ICE_SPINNER, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ], [Species.CARNIVINE]: [ Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.COIL, Moves.SAPPY_SEED ], [Species.FINNEON]: [ Moves.QUIVER_DANCE, Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.ORIGIN_PULSE ], [Species.MANTYKE]: [ Moves.SPLISHY_SPLASH, Moves.FREEZY_FROST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], - [Species.SNOVER]: [ Moves.HIGH_HORSEPOWER, Moves.STRENGTH_SAP, Moves.MATCHA_GOTCHA, Moves.SAPPY_SEED ], + [Species.SNOVER]: [ Moves.LANDS_WRATH, Moves.POWDER, Moves.CALM_MIND, Moves.MATCHA_GOTCHA ], [Species.ROTOM]: [ Moves.STRENGTH_SAP, Moves.FIERY_DANCE, Moves.SPLISHY_SPLASH, Moves.ELECTRO_DRIFT ], - [Species.UXIE]: [ Moves.COSMIC_POWER, Moves.BODY_PRESS, Moves.RECOVER, Moves.SPARKLY_SWIRL ], + [Species.UXIE]: [ Moves.COSMIC_POWER, Moves.SECRET_SWORD, Moves.RECOVER, Moves.SPARKLY_SWIRL ], [Species.MESPRIT]: [ Moves.TAIL_GLOW, Moves.AURA_SPHERE, Moves.RECOVER, Moves.LUMINA_CRASH ], [Species.AZELF]: [ Moves.PSYSTRIKE, Moves.ICE_BEAM, Moves.MOONBLAST, Moves.TAIL_GLOW ], [Species.DIALGA]: [ Moves.CORE_ENFORCER, Moves.TAKE_HEART, Moves.RECOVER, Moves.MAKE_IT_RAIN ], - [Species.PALKIA]: [ Moves.RECOVER, Moves.TAKE_HEART, Moves.FREEZE_DRY, Moves.ORIGIN_PULSE ], - [Species.HEATRAN]: [ Moves.MATCHA_GOTCHA, Moves.RECOVER, Moves.TACHYON_CUTTER, Moves.TORCH_SONG ], + [Species.PALKIA]: [ Moves.MALIGNANT_CHAIN, Moves.TAKE_HEART, Moves.RECOVER, Moves.ORIGIN_PULSE ], + [Species.HEATRAN]: [ Moves.MATCHA_GOTCHA, Moves.RECOVER, Moves.ERUPTION, Moves.TACHYON_CUTTER ], [Species.REGIGIGAS]: [ Moves.SKILL_SWAP, Moves.RECOVER, Moves.EXTREME_SPEED, Moves.GIGATON_HAMMER ], [Species.GIRATINA]: [ Moves.DRAGON_DANCE, Moves.GLAIVE_RUSH, Moves.RECOVER, Moves.SPECTRAL_THIEF ], - [Species.CRESSELIA]: [ Moves.COSMIC_POWER, Moves.SECRET_SWORD, Moves.SIZZLY_SLIDE, Moves.LUMINA_CRASH ], - [Species.PHIONE]: [ Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.SPLISHY_SPLASH, Moves.QUIVER_DANCE ], - [Species.MANAPHY]: [ Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.SPLISHY_SPLASH, Moves.QUIVER_DANCE ], + [Species.CRESSELIA]: [ Moves.COSMIC_POWER, Moves.BODY_PRESS, Moves.SIZZLY_SLIDE, Moves.LUMINA_CRASH ], + [Species.PHIONE]: [ Moves.BOUNCY_BUBBLE, Moves.FREEZE_DRY, Moves.STORED_POWER, Moves.ORIGIN_PULSE ], + [Species.MANAPHY]: [ Moves.BOUNCY_BUBBLE, Moves.FROST_BREATH, Moves.WILDBOLT_STORM, Moves.ORIGIN_PULSE ], [Species.DARKRAI]: [ Moves.FIERY_WRATH, Moves.MOONBLAST, Moves.FIERY_DANCE, Moves.MAKE_IT_RAIN ], [Species.SHAYMIN]: [ Moves.MATCHA_GOTCHA, Moves.FIERY_DANCE, Moves.AEROBLAST, Moves.QUIVER_DANCE ], [Species.ARCEUS]: [ Moves.NO_RETREAT, Moves.COLLISION_COURSE, Moves.ASTRAL_BARRAGE, Moves.MULTI_ATTACK ], + [Species.VICTINI]: [ Moves.BLUE_FLARE, Moves.BOLT_STRIKE, Moves.LUSTER_PURGE, Moves.VICTORY_DANCE ], [Species.SNIVY]: [ Moves.FLAMETHROWER, Moves.CLANGING_SCALES, Moves.MAKE_IT_RAIN, Moves.FLEUR_CANNON ], - [Species.TEPIG]: [ Moves.WAVE_CRASH, Moves.VOLT_TACKLE, Moves.DRAIN_PUNCH, Moves.VICTORY_DANCE ], + [Species.TEPIG]: [ Moves.WAVE_CRASH, Moves.VOLT_TACKLE, Moves.AXE_KICK, Moves.VICTORY_DANCE ], [Species.OSHAWOTT]: [ Moves.TRIPLE_AXEL, Moves.SHELL_SIDE_ARM, Moves.SACRED_SWORD, Moves.SHELL_SMASH ], [Species.PATRAT]: [ Moves.FAKE_OUT, Moves.SWORDS_DANCE, Moves.DYNAMIC_PUNCH, Moves.EXTREME_SPEED ], [Species.LILLIPUP]: [ Moves.CLOSE_COMBAT, Moves.BODY_SLAM, Moves.HIGH_HORSEPOWER, Moves.LAST_RESPECTS ], @@ -262,13 +266,13 @@ export const speciesEggMoves = { [Species.PANSAGE]: [ Moves.SWORDS_DANCE, Moves.FIRE_LASH, Moves.EARTHQUAKE, Moves.IVY_CUDGEL ], [Species.PANSEAR]: [ Moves.NASTY_PLOT, Moves.HYDRO_STEAM, Moves.SCORCHING_SANDS, Moves.TORCH_SONG ], [Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.STEAM_ERUPTION ], - [Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.EARTH_POWER, Moves.MYSTICAL_POWER ], + [Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.LUNAR_BLESSING, Moves.MYSTICAL_POWER ], [Species.PIDOVE]: [ Moves.GUNK_SHOT, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], [Species.BLITZLE]: [ Moves.HORN_LEECH, Moves.SWORDS_DANCE, Moves.FLARE_BLITZ, Moves.BOLT_STRIKE ], [Species.ROGGENROLA]: [ Moves.BODY_PRESS, Moves.CURSE, Moves.SHORE_UP, Moves.DIAMOND_STORM ], [Species.WOOBAT]: [ Moves.ESPER_WING, Moves.STORED_POWER, Moves.MYSTICAL_FIRE, Moves.OBLIVION_WING ], - [Species.DRILBUR]: [ Moves.IRON_HEAD, Moves.MOUNTAIN_GALE, Moves.SHIFT_GEAR, Moves.THOUSAND_ARROWS ], - [Species.AUDINO]: [ Moves.FOLLOW_ME, Moves.MOONBLAST, Moves.WISH, Moves.LUNAR_BLESSING ], + [Species.DRILBUR]: [ Moves.METEOR_MASH, Moves.MOUNTAIN_GALE, Moves.SHIFT_GEAR, Moves.PRECIPICE_BLADES ], + [Species.AUDINO]: [ Moves.TAKE_HEART, Moves.MOONBLAST, Moves.WISH, Moves.MATCHA_GOTCHA ], [Species.TIMBURR]: [ Moves.MACH_PUNCH, Moves.DRAIN_PUNCH, Moves.ICE_HAMMER, Moves.DOUBLE_IRON_BASH ], [Species.TYMPOLE]: [ Moves.JET_PUNCH, Moves.HIGH_HORSEPOWER, Moves.BULK_UP, Moves.SURGING_STRIKES ], [Species.THROH]: [ Moves.MACH_PUNCH, Moves.SLACK_OFF, Moves.METEOR_MASH, Moves.RAGE_FIST ], @@ -278,41 +282,41 @@ export const speciesEggMoves = { [Species.COTTONEE]: [ Moves.POLLEN_PUFF, Moves.PARTING_SHOT, Moves.SLEEP_POWDER, Moves.SEED_FLARE ], [Species.PETILIL]: [ Moves.THUNDEROUS_KICK, Moves.SPARKLING_ARIA, Moves.FIERY_DANCE, Moves.FLOWER_TRICK ], [Species.BASCULIN]: [ Moves.LAST_RESPECTS, Moves.CLOSE_COMBAT, Moves.SPLISHY_SPLASH, Moves.NO_RETREAT ], - [Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.HIGH_HORSEPOWER, Moves.FIRE_LASH, Moves.WICKED_BLOW ], - [Species.DARUMAKA]: [ Moves.DRAIN_PUNCH, Moves.ZIPPY_ZAP, Moves.EARTHQUAKE, Moves.PYRO_BALL ], + [Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.WICKED_BLOW ], + [Species.DARUMAKA]: [ Moves.DRAIN_PUNCH, Moves.ZIPPY_ZAP, Moves.HEADLONG_RUSH, Moves.PYRO_BALL ], [Species.MARACTUS]: [ Moves.EARTH_POWER, Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.SEED_FLARE ], [Species.DWEBBLE]: [ Moves.CRABHAMMER, Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.MIGHTY_CLEAVE ], [Species.SCRAGGY]: [ Moves.SUCKER_PUNCH, Moves.BULLET_PUNCH, Moves.NOXIOUS_TORQUE, Moves.VICTORY_DANCE ], - [Species.SIGILYPH]: [ Moves.MOONBLAST, Moves.CALM_MIND, Moves.FREEZING_GLARE, Moves.OBLIVION_WING ], + [Species.SIGILYPH]: [ Moves.MOONBLAST, Moves.CALM_MIND, Moves.ESPER_WING, Moves.OBLIVION_WING ], [Species.YAMASK]: [ Moves.STRENGTH_SAP, Moves.GLARE, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ], [Species.TIRTOUGA]: [ Moves.ICE_SPINNER, Moves.AQUA_STEP, Moves.SHORE_UP, Moves.MIGHTY_CLEAVE ], [Species.ARCHEN]: [ Moves.ROOST, Moves.EARTHQUAKE, Moves.FLOATY_FALL, Moves.MIGHTY_CLEAVE ], [Species.TRUBBISH]: [ Moves.COIL, Moves.RECOVER, Moves.DIRE_CLAW, Moves.GIGATON_HAMMER ], - [Species.ZORUA]: [ Moves.FLAMETHROWER, Moves.MOONBLAST, Moves.AURA_SPHERE, Moves.FIERY_WRATH ], + [Species.ZORUA]: [ Moves.MALIGNANT_CHAIN, Moves.MOONBLAST, Moves.SECRET_SWORD, Moves.FIERY_WRATH ], [Species.MINCCINO]: [ Moves.ICICLE_SPEAR, Moves.TIDY_UP, Moves.KNOCK_OFF, Moves.POPULATION_BOMB ], [Species.GOTHITA]: [ Moves.RECOVER, Moves.MOONBLAST, Moves.AURA_SPHERE, Moves.LUMINA_CRASH ], - [Species.SOLOSIS]: [ Moves.EXPANDING_FORCE, Moves.TRICK_ROOM, Moves.AURA_SPHERE, Moves.LIGHT_OF_RUIN ], - [Species.DUCKLETT]: [ Moves.SPLISHY_SPLASH, Moves.EARTH_POWER, Moves.WILDBOLT_STORM, Moves.QUIVER_DANCE ], + [Species.SOLOSIS]: [ Moves.MIST_BALL, Moves.SPEED_SWAP, Moves.FLAMETHROWER, Moves.LIGHT_OF_RUIN ], + [Species.DUCKLETT]: [ Moves.SPLISHY_SPLASH, Moves.SANDSEAR_STORM, Moves.WILDBOLT_STORM, Moves.QUIVER_DANCE ], [Species.VANILLITE]: [ Moves.EARTH_POWER, Moves.AURORA_VEIL, Moves.CALM_MIND, Moves.SPARKLY_SWIRL ], [Species.DEERLING]: [ Moves.TIDY_UP, Moves.FLOWER_TRICK, Moves.BODY_SLAM, Moves.COMBAT_TORQUE ], - [Species.EMOLGA]: [ Moves.TRIPLE_AXEL, Moves.SPLISHY_SPLASH, Moves.FLOATY_FALL, Moves.AURA_WHEEL ], - [Species.KARRABLAST]: [ Moves.LEECH_LIFE, Moves.BITTER_BLADE, Moves.HIGH_HORSEPOWER, Moves.DOUBLE_IRON_BASH ], + [Species.EMOLGA]: [ Moves.ICICLE_CRASH, Moves.ZING_ZAP, Moves.FLOATY_FALL, Moves.ELECTRIFY ], + [Species.KARRABLAST]: [ Moves.LEECH_LIFE, Moves.BITTER_BLADE, Moves.OBSTRUCT, Moves.DOUBLE_IRON_BASH ], [Species.FOONGUS]: [ Moves.POLLEN_PUFF, Moves.PARTING_SHOT, Moves.FOUL_PLAY, Moves.SAPPY_SEED ], - [Species.FRILLISH]: [ Moves.STRENGTH_SAP, Moves.BUZZY_BUZZ, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], + [Species.FRILLISH]: [ Moves.CALM_MIND, Moves.BUZZY_BUZZ, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], [Species.ALOMOMOLA]: [ Moves.FLIP_TURN, Moves.HEART_SWAP, Moves.GLITZY_GLOW, Moves.REVIVAL_BLESSING ], [Species.JOLTIK]: [ Moves.WILDBOLT_STORM, Moves.PARABOLIC_CHARGE, Moves.EARTH_POWER, Moves.QUIVER_DANCE ], - [Species.FERROSEED]: [ Moves.STRENGTH_SAP, Moves.BODY_PRESS, Moves.SPIKY_SHIELD, Moves.SAPPY_SEED ], - [Species.KLINK]: [ Moves.TRIPLE_AXEL, Moves.HIGH_HORSEPOWER, Moves.FUSION_BOLT, Moves.DOUBLE_IRON_BASH ], + [Species.FERROSEED]: [ Moves.SYNTHESIS, Moves.COMBAT_TORQUE, Moves.SPIKY_SHIELD, Moves.SAPPY_SEED ], + [Species.KLINK]: [ Moves.TRIPLE_AXEL, Moves.HIGH_HORSEPOWER, Moves.RECOVER, Moves.AURA_WHEEL ], [Species.TYNAMO]: [ Moves.SCALD, Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.AURA_WHEEL ], - [Species.ELGYEM]: [ Moves.LUSTER_PURGE, Moves.BADDY_BAD, Moves.AURA_SPHERE, Moves.TAIL_GLOW ], - [Species.LITWICK]: [ Moves.FIERY_DANCE, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.ASTRAL_BARRAGE ], + [Species.ELGYEM]: [ Moves.THUNDERCLAP, Moves.BADDY_BAD, Moves.AURA_SPHERE, Moves.PHOTON_GEYSER ], + [Species.LITWICK]: [ Moves.PARTING_SHOT, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.TORCH_SONG ], [Species.AXEW]: [ Moves.STONE_AXE, Moves.DIRE_CLAW, Moves.BITTER_BLADE, Moves.GLAIVE_RUSH ], [Species.CUBCHOO]: [ Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.ICE_SHARD, Moves.COLLISION_COURSE ], [Species.CRYOGONAL]: [ Moves.FREEZING_GLARE, Moves.AURORA_VEIL, Moves.NASTY_PLOT, Moves.ORIGIN_PULSE ], [Species.SHELMET]: [ Moves.POWER_GEM, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.STEAM_ERUPTION ], - [Species.STUNFISK]: [ Moves.BANEFUL_BUNKER, Moves.SANDSEAR_STORM, Moves.STRENGTH_SAP, Moves.THUNDERCLAP ], - [Species.MIENFOO]: [ Moves.GUNK_SHOT, Moves.SUPERCELL_SLAM, Moves.KNOCK_OFF, Moves.MOUNTAIN_GALE ], - [Species.DRUDDIGON]: [ Moves.FIRE_LASH, Moves.ROOST, Moves.DRAGON_DARTS, Moves.CLANGOROUS_SOUL ], + [Species.STUNFISK]: [ Moves.THUNDERCLAP, Moves.SANDSEAR_STORM, Moves.STRENGTH_SAP, Moves.THUNDER_CAGE ], + [Species.MIENFOO]: [ Moves.GUNK_SHOT, Moves.SUPERCELL_SLAM, Moves.MOUNTAIN_GALE, Moves.WICKED_BLOW ], + [Species.DRUDDIGON]: [ Moves.FIRE_LASH, Moves.MORNING_SUN, Moves.DRAGON_DARTS, Moves.CLANGOROUS_SOUL ], [Species.GOLETT]: [ Moves.SHIFT_GEAR, Moves.DRAIN_PUNCH, Moves.HEADLONG_RUSH, Moves.RAGE_FIST ], [Species.PAWNIARD]: [ Moves.SUCKER_PUNCH, Moves.CEASELESS_EDGE, Moves.BITTER_BLADE, Moves.LAST_RESPECTS ], [Species.BOUFFALANT]: [ Moves.SLACK_OFF, Moves.HIGH_JUMP_KICK, Moves.HEAD_SMASH, Moves.FLARE_BLITZ ], @@ -321,59 +325,62 @@ export const speciesEggMoves = { [Species.HEATMOR]: [ Moves.EARTH_POWER, Moves.OVERHEAT, Moves.THUNDERBOLT, Moves.V_CREATE ], [Species.DURANT]: [ Moves.HIGH_HORSEPOWER, Moves.FIRST_IMPRESSION, Moves.SWORDS_DANCE, Moves.BEHEMOTH_BASH ], [Species.DEINO]: [ Moves.FIERY_WRATH, Moves.ESPER_WING, Moves.SLUDGE_BOMB, Moves.FICKLE_BEAM ], - [Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.MATCHA_GOTCHA, Moves.EARTH_POWER, Moves.TORCH_SONG ], + [Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.MAGMA_STORM, Moves.EARTH_POWER, Moves.MATCHA_GOTCHA ], [Species.COBALION]: [ Moves.BEHEMOTH_BLADE, Moves.MIGHTY_CLEAVE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], [Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], [Species.VIRIZION]: [ Moves.PSYBLADE, Moves.SAPPY_SEED, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], - [Species.TORNADUS]: [ Moves.EARTH_POWER, Moves.PARTING_SHOT, Moves.ICE_BEAM, Moves.OBLIVION_WING ], - [Species.THUNDURUS]: [ Moves.EARTH_POWER, Moves.HURRICANE, Moves.FROST_BREATH, Moves.ELECTRO_SHOT ], + [Species.TORNADUS]: [ Moves.SANDSEAR_STORM, Moves.PARTING_SHOT, Moves.SPLISHY_SPLASH, Moves.OBLIVION_WING ], + [Species.THUNDURUS]: [ Moves.SANDSEAR_STORM, Moves.HURRICANE, Moves.FROST_BREATH, Moves.ELECTRO_SHOT ], [Species.RESHIRAM]: [ Moves.ENERGY_BALL, Moves.TAKE_HEART, Moves.FICKLE_BEAM, Moves.ERUPTION ], [Species.ZEKROM]: [ Moves.TRIPLE_AXEL, Moves.THUNDEROUS_KICK, Moves.DRAGON_HAMMER, Moves.BOLT_BEAK ], [Species.LANDORUS]: [ Moves.STONE_AXE, Moves.FLOATY_FALL, Moves.ROOST, Moves.BLEAKWIND_STORM ], [Species.KYUREM]: [ Moves.DRAGON_DARTS, Moves.GLACIAL_LANCE, Moves.NO_RETREAT, Moves.DRAGON_ENERGY ], - [Species.KELDEO]: [ Moves.BOUNCY_BUBBLE, Moves.THUNDERBOLT, Moves.FREEZE_DRY, Moves.STEAM_ERUPTION ], + [Species.KELDEO]: [ Moves.BOUNCY_BUBBLE, Moves.THUNDERBOLT, Moves.ICE_BEAM, Moves.STEAM_ERUPTION ], [Species.MELOETTA]: [ Moves.BODY_SLAM, Moves.TORCH_SONG, Moves.TRIPLE_ARROWS, Moves.BOOMBURST ], - [Species.GENESECT]: [ Moves.EXTREME_SPEED, Moves.U_TURN, Moves.TACHYON_CUTTER, Moves.TAIL_GLOW ], - [Species.CHESPIN]: [ Moves.BODY_PRESS, Moves.SYNTHESIS, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED ], - [Species.FENNEKIN]: [ Moves.EXPANDING_FORCE, Moves.MOONBLAST, Moves.THUNDERBOLT, Moves.TORCH_SONG ], + [Species.GENESECT]: [ Moves.EXTREME_SPEED, Moves.SHIFT_GEAR, Moves.BEHEMOTH_BASH, Moves.TACHYON_CUTTER ], + + [Species.CHESPIN]: [ Moves.COMBAT_TORQUE, Moves.SYNTHESIS, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED ], + [Species.FENNEKIN]: [ Moves.TWIN_BEAM, Moves.FIERY_DANCE, Moves.THUNDERBOLT, Moves.SPARKLY_SWIRL ], [Species.FROAKIE]: [ Moves.MOONBLAST, Moves.SHELL_SIDE_ARM, Moves.FIERY_WRATH, Moves.STEAM_ERUPTION ], [Species.BUNNELBY]: [ Moves.DRAIN_PUNCH, Moves.TIDY_UP, Moves.FACADE, Moves.EXTREME_SPEED ], [Species.FLETCHLING]: [ Moves.DRILL_RUN, Moves.BURNING_BULWARK, Moves.HEAD_SMASH, Moves.VOLT_TACKLE ], [Species.SCATTERBUG]: [ Moves.FOCUS_BLAST, Moves.AFTER_YOU, Moves.DECORATE, Moves.BLIZZARD ], - [Species.LITLEO]: [ Moves.EARTH_POWER, Moves.NASTY_PLOT, Moves.YAWN, Moves.TORCH_SONG ], + [Species.LITLEO]: [ Moves.EARTH_POWER, Moves.NASTY_PLOT, Moves.BURNING_BULWARK, Moves.BLUE_FLARE ], [Species.FLABEBE]: [ Moves.GLITZY_GLOW, Moves.MYSTICAL_FIRE, Moves.TAKE_HEART, Moves.SEED_FLARE ], [Species.SKIDDO]: [ Moves.HIGH_HORSEPOWER, Moves.GRASSY_GLIDE, Moves.STONE_AXE, Moves.SAPPY_SEED ], [Species.PANCHAM]: [ Moves.DRAIN_PUNCH, Moves.SUCKER_PUNCH, Moves.METEOR_MASH, Moves.WICKED_BLOW ], [Species.FURFROU]: [ Moves.TIDY_UP, Moves.SLACK_OFF, Moves.COMBAT_TORQUE, Moves.MULTI_ATTACK ], [Species.ESPURR]: [ Moves.LUSTER_PURGE, Moves.MOONBLAST, Moves.AURA_SPHERE, Moves.DARK_VOID ], [Species.HONEDGE]: [ Moves.TACHYON_CUTTER, Moves.SHADOW_BONE, Moves.BITTER_BLADE, Moves.BEHEMOTH_BLADE ], - [Species.SPRITZEE]: [ Moves.SPEED_SWAP, Moves.TORCH_SONG, Moves.ROOST, Moves.REVIVAL_BLESSING ], + [Species.SPRITZEE]: [ Moves.SPEED_SWAP, Moves.REVIVAL_BLESSING, Moves.ROOST, Moves.TORCH_SONG ], [Species.SWIRLIX]: [ Moves.BELLY_DRUM, Moves.HEADLONG_RUSH, Moves.MAGICAL_TORQUE, Moves.REVIVAL_BLESSING ], [Species.INKAY]: [ Moves.POWER_TRIP, Moves.SPIN_OUT, Moves.RECOVER, Moves.PSYCHO_BOOST ], [Species.BINACLE]: [ Moves.TRIPLE_AXEL, Moves.CRABHAMMER, Moves.DIRE_CLAW, Moves.MIGHTY_CLEAVE ], [Species.SKRELP]: [ Moves.STRENGTH_SAP, Moves.TRICK_ROOM, Moves.CALM_MIND, Moves.CORE_ENFORCER ], - [Species.CLAUNCHER]: [ Moves.SHELL_SMASH, Moves.ARMOR_CANNON, Moves.WATER_SHURIKEN, Moves.ORIGIN_PULSE ], + [Species.CLAUNCHER]: [ Moves.SHELL_SMASH, Moves.ARMOR_CANNON, Moves.ENERGY_BALL, Moves.ORIGIN_PULSE ], [Species.HELIOPTILE]: [ Moves.WEATHER_BALL, Moves.HYDRO_STEAM, Moves.EARTH_POWER, Moves.BOOMBURST ], [Species.TYRUNT]: [ Moves.DRAGON_HAMMER, Moves.FLARE_BLITZ, Moves.VOLT_TACKLE, Moves.SHIFT_GEAR ], - [Species.AMAURA]: [ Moves.RECOVER, Moves.AURORA_VEIL, Moves.POWER_GEM, Moves.GEOMANCY ], + [Species.AMAURA]: [ Moves.RECOVER, Moves.WRING_OUT, Moves.POWER_GEM, Moves.GEOMANCY ], [Species.HAWLUCHA]: [ Moves.TRIPLE_AXEL, Moves.HIGH_HORSEPOWER, Moves.FLOATY_FALL, Moves.WICKED_BLOW ], [Species.DEDENNE]: [ Moves.BOOMBURST, Moves.FAKE_OUT, Moves.NASTY_PLOT, Moves.REVIVAL_BLESSING ], [Species.CARBINK]: [ Moves.BODY_PRESS, Moves.SHORE_UP, Moves.SPARKLY_SWIRL, Moves.DIAMOND_STORM ], - [Species.GOOMY]: [ Moves.SCALD, Moves.RECOVER, Moves.CALM_MIND, Moves.MAKE_IT_RAIN ], + [Species.GOOMY]: [ Moves.DRAGON_HAMMER, Moves.RECOVER, Moves.CALM_MIND, Moves.MAKE_IT_RAIN ], [Species.KLEFKI]: [ Moves.HEAL_BELL, Moves.ENCORE, Moves.INSTRUCT, Moves.TOPSY_TURVY ], - [Species.PHANTUMP]: [ Moves.RAGE_FIST, Moves.TRICK_ROOM, Moves.SYNTHESIS, Moves.SAPPY_SEED ], + [Species.PHANTUMP]: [ Moves.RAGE_FIST, Moves.SLEEP_POWDER, Moves.SYNTHESIS, Moves.SAPPY_SEED ], [Species.PUMPKABOO]: [ Moves.SPIRIT_SHACKLE, Moves.FIRE_LASH, Moves.DIRE_CLAW, Moves.SAPPY_SEED ], [Species.BERGMITE]: [ Moves.STONE_AXE, Moves.METAL_BURST, Moves.BODY_PRESS, Moves.GLACIAL_LANCE ], [Species.NOIBAT]: [ Moves.AEROBLAST, Moves.OVERDRIVE, Moves.NASTY_PLOT, Moves.CLANGING_SCALES ], [Species.XERNEAS]: [ Moves.EARTH_POWER, Moves.SPRINGTIDE_STORM, Moves.STRENGTH_SAP, Moves.TAIL_GLOW ], [Species.YVELTAL]: [ Moves.SHELL_SIDE_ARM, Moves.POWER_TRIP, Moves.FIERY_WRATH, Moves.CLANGOROUS_SOUL ], [Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.HEAL_ORDER, Moves.CLANGOROUS_SOUL, Moves.DOUBLE_IRON_BASH ], - [Species.DIANCIE]: [ Moves.MAGICAL_TORQUE, Moves.AURA_SPHERE, Moves.SHORE_UP, Moves.GEOMANCY ], + [Species.DIANCIE]: [ Moves.MAGICAL_TORQUE, Moves.FIERY_DANCE, Moves.SHORE_UP, Moves.GEOMANCY ], [Species.HOOPA]: [ Moves.PHOTON_GEYSER, Moves.SECRET_SWORD, Moves.FIERY_WRATH, Moves.SHELL_SMASH ], [Species.VOLCANION]: [ Moves.HYDRO_STEAM, Moves.CALM_MIND, Moves.ENERGY_BALL, Moves.MAGMA_STORM ], + [Species.ETERNAL_FLOETTE]: [ Moves.MIND_BLOWN, Moves.CHLOROBLAST, Moves.LUSTER_PURGE, Moves.QUIVER_DANCE ], + [Species.ROWLET]: [ Moves.THOUSAND_ARROWS, Moves.SHADOW_BONE, Moves.FIRST_IMPRESSION, Moves.VICTORY_DANCE ], - [Species.LITTEN]: [ Moves.FAKE_OUT, Moves.PARTING_SHOT, Moves.MORNING_SUN, Moves.SACRED_FIRE ], - [Species.POPPLIO]: [ Moves.PSYCHIC_NOISE, Moves.BOUNCY_BUBBLE, Moves.ALLURING_VOICE, Moves.TORCH_SONG ], + [Species.LITTEN]: [ Moves.SUCKER_PUNCH, Moves.PARTING_SHOT, Moves.SLACK_OFF, Moves.SACRED_FIRE ], + [Species.POPPLIO]: [ Moves.PSYCHIC_NOISE, Moves.BOUNCY_BUBBLE, Moves.OVERDRIVE, Moves.TORCH_SONG ], [Species.PIKIPEK]: [ Moves.DUAL_WINGBEAT, Moves.BONE_RUSH, Moves.BURNING_BULWARK, Moves.POPULATION_BOMB ], [Species.YUNGOOS]: [ Moves.EXTREME_SPEED, Moves.KNOCK_OFF, Moves.TIDY_UP, Moves.MULTI_ATTACK ], [Species.GRUBBIN]: [ Moves.ICE_BEAM, Moves.EARTH_POWER, Moves.THUNDERCLAP, Moves.QUIVER_DANCE ], @@ -381,7 +388,7 @@ export const speciesEggMoves = { [Species.ORICORIO]: [ Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.THUNDERCLAP, Moves.OBLIVION_WING ], [Species.CUTIEFLY]: [ Moves.STICKY_WEB, Moves.SLEEP_POWDER, Moves.HEAT_WAVE, Moves.SPARKLY_SWIRL ], [Species.ROCKRUFF]: [ Moves.HIGH_HORSEPOWER, Moves.TIDY_UP, Moves.ICE_SPINNER, Moves.MIGHTY_CLEAVE ], - [Species.WISHIWASHI]: [ Moves.HEAL_ORDER, Moves.ICE_SPINNER, Moves.DRAGON_DANCE, Moves.JET_PUNCH ], + [Species.WISHIWASHI]: [ Moves.HEAL_ORDER, Moves.FREEZE_DRY, Moves.WATER_SHURIKEN, Moves.TAIL_GLOW ], [Species.MAREANIE]: [ Moves.CEASELESS_EDGE, Moves.SIZZLY_SLIDE, Moves.BODY_PRESS, Moves.LEECH_SEED ], [Species.MUDBRAY]: [ Moves.BODY_PRESS, Moves.YAWN, Moves.SHORE_UP, Moves.THOUSAND_WAVES ], [Species.DEWPIDER]: [ Moves.JET_PUNCH, Moves.SILK_TRAP, Moves.SWORDS_DANCE, Moves.AQUA_STEP ], @@ -392,27 +399,27 @@ export const speciesEggMoves = { [Species.BOUNSWEET]: [ Moves.TRIPLE_AXEL, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK, Moves.SAPPY_SEED ], [Species.COMFEY]: [ Moves.REVIVAL_BLESSING, Moves.TAKE_HEART, Moves.STRENGTH_SAP, Moves.MATCHA_GOTCHA ], [Species.ORANGURU]: [ Moves.JUNGLE_HEALING, Moves.YAWN, Moves.FOLLOW_ME, Moves.LUMINA_CRASH ], - [Species.PASSIMIAN]: [ Moves.FAKE_OUT, Moves.SUCKER_PUNCH, Moves.ZING_ZAP, Moves.PYRO_BALL ], + [Species.PASSIMIAN]: [ Moves.PYRO_BALL, Moves.SUCKER_PUNCH, Moves.ZING_ZAP, Moves.VICTORY_DANCE ], [Species.WIMPOD]: [ Moves.TRIPLE_AXEL, Moves.OBSTRUCT, Moves.JET_PUNCH, Moves.SURGING_STRIKES ], - [Species.SANDYGAST]: [ Moves.SANDSEAR_STORM, Moves.SPLISHY_SPLASH, Moves.TAKE_HEART, Moves.SALT_CURE ], + [Species.SANDYGAST]: [ Moves.BITTER_MALICE, Moves.SPLISHY_SPLASH, Moves.TAKE_HEART, Moves.SALT_CURE ], [Species.PYUKUMUKU]: [ Moves.COMEUPPANCE, Moves.BANEFUL_BUNKER, Moves.TOXIC_SPIKES, Moves.SALT_CURE ], - [Species.TYPE_NULL]: [ Moves.DIRE_CLAW, Moves.RECOVER, Moves.EXTREME_SPEED, Moves.SHELL_SMASH ], + [Species.TYPE_NULL]: [ Moves.DIRE_CLAW, Moves.RECOVER, Moves.COMBAT_TORQUE, Moves.NO_RETREAT ], [Species.MINIOR]: [ Moves.EARTH_POWER, Moves.FLOATY_FALL, Moves.ZING_ZAP, Moves.DIAMOND_STORM ], [Species.KOMALA]: [ Moves.SLACK_OFF, Moves.EXTREME_SPEED, Moves.KNOCK_OFF, Moves.COLLISION_COURSE ], [Species.TURTONATOR]: [ Moves.BURNING_BULWARK, Moves.MORNING_SUN, Moves.BODY_PRESS, Moves.CORE_ENFORCER ], [Species.TOGEDEMARU]: [ Moves.FAKE_OUT, Moves.METAL_BURST, Moves.METEOR_MASH, Moves.AURA_WHEEL ], - [Species.MIMIKYU]: [ Moves.SPIRIT_BREAK, Moves.TIDY_UP, Moves.BITTER_BLADE, Moves.SPECTRAL_THIEF ], + [Species.MIMIKYU]: [ Moves.SPIRIT_BREAK, Moves.TIDY_UP, Moves.FIRE_LASH, Moves.SPECTRAL_THIEF ], [Species.BRUXISH]: [ Moves.PLAY_ROUGH, Moves.FIRE_FANG, Moves.DRAGON_DANCE, Moves.SURGING_STRIKES ], [Species.DRAMPA]: [ Moves.SLACK_OFF, Moves.TRICK_ROOM, Moves.CORE_ENFORCER, Moves.BOOMBURST ], - [Species.DHELMISE]: [ Moves.SHADOW_BONE, Moves.STRENGTH_SAP, Moves.LIQUIDATION, Moves.SAPPY_SEED ], + [Species.DHELMISE]: [ Moves.SHADOW_BONE, Moves.IVY_CUDGEL, Moves.TRIPLE_DIVE, Moves.STRENGTH_SAP ], [Species.JANGMO_O]: [ Moves.BODY_PRESS, Moves.SHELL_SIDE_ARM, Moves.SECRET_SWORD, Moves.GLAIVE_RUSH ], - [Species.TAPU_KOKO]: [ Moves.MAGICAL_TORQUE, Moves.TRIPLE_AXEL, Moves.RISING_VOLTAGE, Moves.BOLT_STRIKE ], + [Species.TAPU_KOKO]: [ Moves.MAGICAL_TORQUE, Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.BOLT_STRIKE ], [Species.TAPU_LELE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.HEAT_WAVE, Moves.EXPANDING_FORCE ], [Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.DRAIN_PUNCH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ], [Species.TAPU_FINI]: [ Moves.AURA_SPHERE, Moves.EARTH_POWER, Moves.RECOVER, Moves.QUIVER_DANCE ], [Species.COSMOG]: [ Moves.PHOTON_GEYSER, Moves.PRECIPICE_BLADES, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE ], [Species.NIHILEGO]: [ Moves.STRENGTH_SAP, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.QUIVER_DANCE ], - [Species.BUZZWOLE]: [ Moves.FIRST_IMPRESSION, Moves.COMBAT_TORQUE, Moves.ROCK_WRECKER, Moves.DOUBLE_IRON_BASH ], + [Species.BUZZWOLE]: [ Moves.FIRST_IMPRESSION, Moves.COMBAT_TORQUE, Moves.ROCK_BLAST, Moves.DOUBLE_IRON_BASH ], [Species.PHEROMOSA]: [ Moves.SECRET_SWORD, Moves.MAKE_IT_RAIN, Moves.ATTACK_ORDER, Moves.DIAMOND_STORM ], [Species.XURKITREE]: [ Moves.FLAMETHROWER, Moves.GIGA_DRAIN, Moves.TAIL_GLOW, Moves.THUNDERCLAP ], [Species.CELESTEELA]: [ Moves.RECOVER, Moves.BUZZY_BUZZ, Moves.SANDSEAR_STORM, Moves.OBLIVION_WING ], @@ -421,11 +428,19 @@ export const speciesEggMoves = { [Species.NECROZMA]: [ Moves.DYNAMAX_CANNON, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE, Moves.CLANGOROUS_SOUL ], [Species.MAGEARNA]: [ Moves.STRENGTH_SAP, Moves.EARTH_POWER, Moves.MOONBLAST, Moves.MAKE_IT_RAIN ], [Species.MARSHADOW]: [ Moves.POWER_UP_PUNCH, Moves.TRIPLE_AXEL, Moves.METEOR_MASH, Moves.STORM_THROW ], - [Species.POIPOLE]: [ Moves.CORE_ENFORCER, Moves.ICE_BEAM, Moves.SEARING_SHOT, Moves.MALIGNANT_CHAIN ], + [Species.POIPOLE]: [ Moves.MALIGNANT_CHAIN, Moves.ICE_BEAM, Moves.ARMOR_CANNON, Moves.CLANGING_SCALES ], [Species.STAKATAKA]: [ Moves.HEAVY_SLAM, Moves.SHORE_UP, Moves.CURSE, Moves.SALT_CURE ], - [Species.BLACEPHALON]: [ Moves.NASTY_PLOT, Moves.AURA_SPHERE, Moves.CHLOROBLAST, Moves.ASTRAL_BARRAGE ], + [Species.BLACEPHALON]: [ Moves.STEEL_BEAM, Moves.MOONBLAST, Moves.CHLOROBLAST, Moves.MOONGEIST_BEAM ], [Species.ZERAORA]: [ Moves.SWORDS_DANCE, Moves.TRIPLE_AXEL, Moves.BOLT_STRIKE, Moves.PYRO_BALL ], [Species.MELTAN]: [ Moves.BULLET_PUNCH, Moves.DRAIN_PUNCH, Moves.BULK_UP, Moves.PLASMA_FISTS ], + [Species.ALOLA_RATTATA]: [ Moves.FALSE_SURRENDER, Moves.PSYCHIC_FANGS, Moves.COIL, Moves.EXTREME_SPEED ], + [Species.ALOLA_SANDSHREW]: [ Moves.SPIKY_SHIELD, Moves.AQUA_CUTTER, Moves.SHIFT_GEAR, Moves.GLACIAL_LANCE ], + [Species.ALOLA_VULPIX]: [ Moves.MOONBLAST, Moves.PARTING_SHOT, Moves.EARTH_POWER, Moves.REVIVAL_BLESSING ], + [Species.ALOLA_DIGLETT]: [ Moves.THOUSAND_WAVES, Moves.SWORDS_DANCE, Moves.TRIPLE_DIVE, Moves.MOUNTAIN_GALE ], + [Species.ALOLA_MEOWTH]: [ Moves.BADDY_BAD, Moves.BUZZY_BUZZ, Moves.PARTING_SHOT, Moves.MAKE_IT_RAIN ], + [Species.ALOLA_GEODUDE]: [ Moves.THOUSAND_WAVES, Moves.BULK_UP, Moves.STONE_AXE, Moves.EXTREME_SPEED ], + [Species.ALOLA_GRIMER]: [ Moves.SUCKER_PUNCH, Moves.BARB_BARRAGE, Moves.RECOVER, Moves.SURGING_STRIKES ], + [Species.GROOKEY]: [ Moves.HIGH_HORSEPOWER, Moves.CLANGOROUS_SOUL, Moves.GRASSY_GLIDE, Moves.SAPPY_SEED ], [Species.SCORBUNNY]: [ Moves.EXTREME_SPEED, Moves.HIGH_JUMP_KICK, Moves.TRIPLE_AXEL, Moves.BOLT_STRIKE ], [Species.SOBBLE]: [ Moves.AEROBLAST, Moves.FROST_BREATH, Moves.ENERGY_BALL, Moves.NASTY_PLOT ], @@ -433,15 +448,15 @@ export const speciesEggMoves = { [Species.ROOKIDEE]: [ Moves.ROOST, Moves.BODY_PRESS, Moves.KINGS_SHIELD, Moves.BEHEMOTH_BASH ], [Species.BLIPBUG]: [ Moves.HEAL_ORDER, Moves.LUSTER_PURGE, Moves.SLEEP_POWDER, Moves.TAIL_GLOW ], [Species.NICKIT]: [ Moves.BADDY_BAD, Moves.FLAMETHROWER, Moves.SPARKLY_SWIRL, Moves.MAKE_IT_RAIN ], - [Species.GOSSIFLEUR]: [ Moves.TAILWIND, Moves.STRENGTH_SAP, Moves.SAPPY_SEED, Moves.SEED_FLARE ], + [Species.GOSSIFLEUR]: [ Moves.PARTING_SHOT, Moves.STRENGTH_SAP, Moves.SAPPY_SEED, Moves.SEED_FLARE ], [Species.WOOLOO]: [ Moves.PSYSHIELD_BASH, Moves.MILK_DRINK, Moves.BODY_PRESS, Moves.MULTI_ATTACK ], - [Species.CHEWTLE]: [ Moves.ICE_FANG, Moves.ACCELEROCK, Moves.SHELL_SMASH, Moves.FISHIOUS_REND ], + [Species.CHEWTLE]: [ Moves.ICE_FANG, Moves.PSYCHIC_FANGS, Moves.SHELL_SMASH, Moves.MIGHTY_CLEAVE ], [Species.YAMPER]: [ Moves.ICE_FANG, Moves.SWORDS_DANCE, Moves.THUNDER_FANG, Moves.BOLT_STRIKE ], [Species.ROLYCOLY]: [ Moves.BITTER_BLADE, Moves.BODY_PRESS, Moves.BULK_UP, Moves.DIAMOND_STORM ], - [Species.APPLIN]: [ Moves.MATCHA_GOTCHA, Moves.DRAGON_HAMMER, Moves.FLOWER_TRICK, Moves.STRENGTH_SAP ], + [Species.APPLIN]: [ Moves.CORE_ENFORCER, Moves.DRAGON_HAMMER, Moves.FLOWER_TRICK, Moves.MATCHA_GOTCHA ], [Species.SILICOBRA]: [ Moves.SHORE_UP, Moves.SHED_TAIL, Moves.MOUNTAIN_GALE, Moves.THOUSAND_ARROWS ], [Species.CRAMORANT]: [ Moves.APPLE_ACID, Moves.SURF, Moves.SCORCHING_SANDS, Moves.OBLIVION_WING ], - [Species.ARROKUDA]: [ Moves.SUPERCELL_SLAM, Moves.KNOCK_OFF, Moves.ICE_SPINNER, Moves.FILLET_AWAY ], + [Species.ARROKUDA]: [ Moves.SUPERCELL_SLAM, Moves.TRIPLE_DIVE, Moves.ICE_SPINNER, Moves.SWORDS_DANCE ], [Species.TOXEL]: [ Moves.NASTY_PLOT, Moves.BUG_BUZZ, Moves.SPARKLING_ARIA, Moves.TORCH_SONG ], [Species.SIZZLIPEDE]: [ Moves.BURNING_BULWARK, Moves.ZING_ZAP, Moves.FIRST_IMPRESSION, Moves.BITTER_BLADE ], [Species.CLOBBOPUS]: [ Moves.STORM_THROW, Moves.JET_PUNCH, Moves.MACH_PUNCH, Moves.SURGING_STRIKES ], @@ -450,19 +465,19 @@ export const speciesEggMoves = { [Species.IMPIDIMP]: [ Moves.ENCORE, Moves.PARTING_SHOT, Moves.TOPSY_TURVY, Moves.WICKED_BLOW ], [Species.MILCERY]: [ Moves.MOONBLAST, Moves.CHILLY_RECEPTION, Moves.EARTH_POWER, Moves.GEOMANCY ], [Species.FALINKS]: [ Moves.COMBAT_TORQUE, Moves.PSYSHIELD_BASH, Moves.HEAL_ORDER, Moves.POPULATION_BOMB ], - [Species.PINCURCHIN]: [ Moves.TRICK_ROOM, Moves.RISING_VOLTAGE, Moves.STRENGTH_SAP, Moves.THUNDERCLAP ], + [Species.PINCURCHIN]: [ Moves.TRICK_ROOM, Moves.VOLT_SWITCH, Moves.STRENGTH_SAP, Moves.THUNDERCLAP ], [Species.SNOM]: [ Moves.FROST_BREATH, Moves.HEAL_ORDER, Moves.EARTH_POWER, Moves.SPORE ], [Species.STONJOURNER]: [ Moves.BODY_PRESS, Moves.HELPING_HAND, Moves.ACCELEROCK, Moves.DIAMOND_STORM ], - [Species.EISCUE]: [ Moves.TRIPLE_AXEL, Moves.AQUA_STEP, Moves.SHELL_SMASH, Moves.GLACIAL_LANCE ], + [Species.EISCUE]: [ Moves.TRIPLE_AXEL, Moves.AQUA_STEP, Moves.AXE_KICK, Moves.SHELL_SMASH ], [Species.INDEEDEE]: [ Moves.MATCHA_GOTCHA, Moves.EXPANDING_FORCE, Moves.MOONBLAST, Moves.REVIVAL_BLESSING ], [Species.MORPEKO]: [ Moves.TRIPLE_AXEL, Moves.OBSTRUCT, Moves.SWORDS_DANCE, Moves.COLLISION_COURSE ], [Species.CUFANT]: [ Moves.LIQUIDATION, Moves.CURSE, Moves.COMBAT_TORQUE, Moves.GIGATON_HAMMER ], - [Species.DRACOZOLT]: [ Moves.TRIPLE_AXEL, Moves.SCALE_SHOT, Moves.FIRE_LASH, Moves.DRAGON_DANCE ], + [Species.DRACOZOLT]: [ Moves.TRIPLE_AXEL, Moves.GUNK_SHOT, Moves.FIRE_LASH, Moves.DRAGON_DANCE ], [Species.ARCTOZOLT]: [ Moves.MOUNTAIN_GALE, Moves.AQUA_STEP, Moves.HIGH_HORSEPOWER, Moves.SHIFT_GEAR ], [Species.DRACOVISH]: [ Moves.TRIPLE_AXEL, Moves.DRAGON_HAMMER, Moves.THUNDER_FANG, Moves.DRAGON_DANCE ], [Species.ARCTOVISH]: [ Moves.ICE_FANG, Moves.THUNDER_FANG, Moves.HIGH_HORSEPOWER, Moves.SHIFT_GEAR ], [Species.DURALUDON]: [ Moves.CORE_ENFORCER, Moves.BODY_PRESS, Moves.RECOVER, Moves.TACHYON_CUTTER ], - [Species.DREEPY]: [ Moves.SHADOW_BONE, Moves.NASTY_PLOT, Moves.FIRE_LASH, Moves.COLLISION_COURSE ], + [Species.DREEPY]: [ Moves.SHADOW_BONE, Moves.POWER_UP_PUNCH, Moves.FIRE_LASH, Moves.DIRE_CLAW ], [Species.ZACIAN]: [ Moves.MAGICAL_TORQUE, Moves.MIGHTY_CLEAVE, Moves.BITTER_BLADE, Moves.PRECIPICE_BLADES ], [Species.ZAMAZENTA]: [ Moves.BULK_UP, Moves.BODY_PRESS, Moves.SLACK_OFF, Moves.DIAMOND_STORM ], [Species.ETERNATUS]: [ Moves.BODY_PRESS, Moves.NASTY_PLOT, Moves.MALIGNANT_CHAIN, Moves.DRAGON_ENERGY ], @@ -470,18 +485,36 @@ export const speciesEggMoves = { [Species.ZARUDE]: [ Moves.SAPPY_SEED, Moves.MIGHTY_CLEAVE, Moves.WICKED_BLOW, Moves.VICTORY_DANCE ], [Species.REGIELEKI]: [ Moves.NASTY_PLOT, Moves.ICE_BEAM, Moves.EARTH_POWER, Moves.ELECTRO_DRIFT ], [Species.REGIDRAGO]: [ Moves.METEOR_MASH, Moves.FLAMETHROWER, Moves.TAKE_HEART, Moves.DRAGON_DARTS ], - [Species.GLASTRIER]: [ Moves.TRICK_ROOM, Moves.SLACK_OFF, Moves.HIGH_HORSEPOWER, Moves.GLACIAL_LANCE ], + [Species.GLASTRIER]: [ Moves.SPEED_SWAP, Moves.SLACK_OFF, Moves.HIGH_HORSEPOWER, Moves.GLACIAL_LANCE ], [Species.SPECTRIER]: [ Moves.EARTH_POWER, Moves.PARTING_SHOT, Moves.AURA_SPHERE, Moves.ASTRAL_BARRAGE ], [Species.CALYREX]: [ Moves.SAPPY_SEED, Moves.RECOVER, Moves.SECRET_SWORD, Moves.PHOTON_GEYSER ], - [Species.ENAMORUS]: [ Moves.FLEUR_CANNON, Moves.TAKE_HEART, Moves.STORED_POWER, Moves.OBLIVION_WING ], + [Species.ENAMORUS]: [ Moves.AEROBLAST, Moves.THOUSAND_ARROWS, Moves.STORED_POWER, Moves.FLEUR_CANNON ], + [Species.GALAR_MEOWTH]: [ Moves.LIQUIDATION, Moves.HORN_LEECH, Moves.BULLET_PUNCH, Moves.BEHEMOTH_BASH ], + [Species.GALAR_PONYTA]: [ Moves.MAGICAL_TORQUE, Moves.EXTREME_SPEED, Moves.FLARE_BLITZ, Moves.PHOTON_GEYSER ], + [Species.GALAR_SLOWPOKE]: [ Moves.SHED_TAIL, Moves.BADDY_BAD, Moves.MOONBLAST, Moves.PHOTON_GEYSER ], + [Species.GALAR_FARFETCHD]: [ Moves.ROOST, Moves.SACRED_SWORD, Moves.KINGS_SHIELD, Moves.BEHEMOTH_BLADE ], + [Species.GALAR_ARTICUNO]: [ Moves.SECRET_SWORD, Moves.NIGHT_DAZE, Moves.ICE_BEAM, Moves.OBLIVION_WING ], + [Species.GALAR_ZAPDOS]: [ Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.ROOST, Moves.BOLT_BEAK ], + [Species.GALAR_MOLTRES]: [ Moves.ROOST, Moves.SLUDGE_BOMB, Moves.FLAMETHROWER, Moves.OBLIVION_WING ], + [Species.GALAR_CORSOLA]: [ Moves.SHELL_SMASH, Moves.AURA_SPHERE, Moves.INFERNAL_PARADE, Moves.ASTRAL_BARRAGE ], + [Species.GALAR_ZIGZAGOON]: [ Moves.CEASELESS_EDGE, Moves.FACADE, Moves.PARTING_SHOT, Moves.EXTREME_SPEED ], + [Species.GALAR_DARUMAKA]: [ Moves.ICE_SPINNER, Moves.ZING_ZAP, Moves.DRAIN_PUNCH, Moves.PYRO_BALL ], + [Species.GALAR_YAMASK]: [ Moves.STRENGTH_SAP, Moves.DIRE_CLAW, Moves.THOUSAND_WAVES, Moves.SPECTRAL_THIEF ], + [Species.GALAR_STUNFISK]: [ Moves.SPIKY_SHIELD, Moves.THOUSAND_ARROWS, Moves.STRENGTH_SAP, Moves.DOUBLE_IRON_BASH ], + [Species.HISUI_GROWLITHE]: [ Moves.WAVE_CRASH, Moves.HEAD_SMASH, Moves.VOLT_TACKLE, Moves.DRAGON_DANCE ], + [Species.HISUI_VOLTORB]: [ Moves.FROST_BREATH, Moves.NASTY_PLOT, Moves.APPLE_ACID, Moves.ELECTRO_DRIFT ], + [Species.HISUI_QWILFISH]: [ Moves.CEASELESS_EDGE, Moves.KNOCK_OFF, Moves.RECOVER, Moves.FISHIOUS_REND ], + [Species.HISUI_SNEASEL]: [ Moves.DRAIN_PUNCH, Moves.KNOCK_OFF, Moves.PSYCHIC_FANGS, Moves.TRIPLE_AXEL ], + [Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.HYPER_VOICE, Moves.PARTING_SHOT, Moves.BLOOD_MOON ], + [Species.SPRIGATITO]: [ Moves.FIRE_LASH, Moves.TRIPLE_AXEL, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ], [Species.FUECOCO]: [ Moves.ALLURING_VOICE, Moves.SLACK_OFF, Moves.OVERDRIVE, Moves.MOONGEIST_BEAM ], [Species.QUAXLY]: [ Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.POWER_TRIP, Moves.THUNDEROUS_KICK ], [Species.LECHONK]: [ Moves.MILK_DRINK, Moves.PSYSHIELD_BASH, Moves.FILLET_AWAY, Moves.MULTI_ATTACK ], [Species.TAROUNTULA]: [ Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.THIEF, Moves.SPORE ], [Species.NYMBLE]: [ Moves.KNOCK_OFF, Moves.FELL_STINGER, Moves.ATTACK_ORDER, Moves.WICKED_BLOW ], - [Species.PAWMI]: [ Moves.DRAIN_PUNCH, Moves.ICE_PUNCH, Moves.MACH_PUNCH, Moves.PLASMA_FISTS ], - [Species.TANDEMAUS]: [ Moves.BATON_PASS, Moves.THIEF, Moves.SIZZLY_SLIDE, Moves.REVIVAL_BLESSING ], + [Species.PAWMI]: [ Moves.DRAIN_PUNCH, Moves.METEOR_MASH, Moves.JET_PUNCH, Moves.PLASMA_FISTS ], + [Species.TANDEMAUS]: [ Moves.BATON_PASS, Moves.COVET, Moves.SIZZLY_SLIDE, Moves.REVIVAL_BLESSING ], [Species.FIDOUGH]: [ Moves.SOFT_BOILED, Moves.HIGH_HORSEPOWER, Moves.SIZZLY_SLIDE, Moves.TIDY_UP ], [Species.SMOLIV]: [ Moves.STRENGTH_SAP, Moves.EARTH_POWER, Moves.CALM_MIND, Moves.BOOMBURST ], [Species.SQUAWKABILLY]: [ Moves.PARTING_SHOT, Moves.EARTHQUAKE, Moves.FLARE_BLITZ, Moves.EXTREME_SPEED ], @@ -497,24 +530,24 @@ export const speciesEggMoves = { [Species.CAPSAKID]: [ Moves.STRENGTH_SAP, Moves.APPLE_ACID, Moves.FROST_BREATH, Moves.TORCH_SONG ], [Species.RELLOR]: [ Moves.HEAL_BLOCK, Moves.RECOVER, Moves.HEAT_WAVE, Moves.LUMINA_CRASH ], [Species.FLITTLE]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.ROOST, Moves.FIERY_DANCE ], - [Species.TINKATINK]: [ Moves.MAGICAL_TORQUE, Moves.PYRO_BALL, Moves.ICE_HAMMER, Moves.SHIFT_GEAR ], + [Species.TINKATINK]: [ Moves.MAGICAL_TORQUE, Moves.PYRO_BALL, Moves.IVY_CUDGEL, Moves.SHIFT_GEAR ], [Species.WIGLETT]: [ Moves.SHELL_SMASH, Moves.ICICLE_CRASH, Moves.SEED_BOMB, Moves.SURGING_STRIKES ], [Species.BOMBIRDIER]: [ Moves.FLOATY_FALL, Moves.SWORDS_DANCE, Moves.SUCKER_PUNCH, Moves.MIGHTY_CLEAVE ], [Species.FINIZEN]: [ Moves.TRIPLE_AXEL, Moves.DRAIN_PUNCH, Moves.HEADLONG_RUSH, Moves.SURGING_STRIKES ], [Species.VAROOM]: [ Moves.COMBAT_TORQUE, Moves.U_TURN, Moves.BLAZING_TORQUE, Moves.NOXIOUS_TORQUE ], - [Species.CYCLIZAR]: [ Moves.BATON_PASS, Moves.BLAZING_TORQUE, Moves.KNOCK_OFF, Moves.CLANGOROUS_SOUL ], + [Species.CYCLIZAR]: [ Moves.PARTING_SHOT, Moves.FIRE_LASH, Moves.MAGICAL_TORQUE, Moves.GLAIVE_RUSH ], [Species.ORTHWORM]: [ Moves.SIZZLY_SLIDE, Moves.COIL, Moves.BODY_PRESS, Moves.SHORE_UP ], [Species.GLIMMET]: [ Moves.CALM_MIND, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.MALIGNANT_CHAIN ], [Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.YAWN, Moves.SHORE_UP, Moves.COLLISION_COURSE ], [Species.FLAMIGO]: [ Moves.THUNDEROUS_KICK, Moves.TRIPLE_AXEL, Moves.FLOATY_FALL, Moves.VICTORY_DANCE ], - [Species.CETODDLE]: [ Moves.MOUNTAIN_GALE, Moves.HIGH_HORSEPOWER, Moves.RECOVER, Moves.DRAGON_DANCE ], + [Species.CETODDLE]: [ Moves.MOUNTAIN_GALE, Moves.HIGH_HORSEPOWER, Moves.SLACK_OFF, Moves.DRAGON_DANCE ], [Species.VELUZA]: [ Moves.PSYBLADE, Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.BITTER_BLADE ], - [Species.DONDOZO]: [ Moves.SOFT_BOILED, Moves.SIZZLY_SLIDE, Moves.TOXIC, Moves.SALT_CURE ], - [Species.TATSUGIRI]: [ Moves.ICE_BEAM, Moves.FILLET_AWAY, Moves.CORE_ENFORCER, Moves.STEAM_ERUPTION ], + [Species.DONDOZO]: [ Moves.SOFT_BOILED, Moves.SIZZLY_SLIDE, Moves.BREAKING_SWIPE, Moves.SALT_CURE ], + [Species.TATSUGIRI]: [ Moves.SLUDGE_BOMB, Moves.FILLET_AWAY, Moves.CORE_ENFORCER, Moves.STEAM_ERUPTION ], [Species.GREAT_TUSK]: [ Moves.STONE_AXE, Moves.MORNING_SUN, Moves.COLLISION_COURSE, Moves.SHIFT_GEAR ], [Species.SCREAM_TAIL]: [ Moves.TORCH_SONG, Moves.GLITZY_GLOW, Moves.MOONLIGHT, Moves.SPARKLY_SWIRL ], [Species.BRUTE_BONNET]: [ Moves.SAPPY_SEED, Moves.STRENGTH_SAP, Moves.EARTHQUAKE, Moves.WICKED_BLOW ], - [Species.FLUTTER_MANE]: [ Moves.MOONLIGHT, Moves.FLAMETHROWER, Moves.EARTH_POWER, Moves.ASTRAL_BARRAGE ], + [Species.FLUTTER_MANE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.MOONGEIST_BEAM ], [Species.SLITHER_WING]: [ Moves.MIGHTY_CLEAVE, Moves.THUNDEROUS_KICK, Moves.FIRE_LASH, Moves.VICTORY_DANCE ], [Species.SANDY_SHOCKS]: [ Moves.MORNING_SUN, Moves.ICE_BEAM, Moves.NASTY_PLOT, Moves.THUNDERCLAP ], [Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.BULK_UP, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ], @@ -523,57 +556,32 @@ export const speciesEggMoves = { [Species.IRON_JUGULIS]: [ Moves.FIERY_WRATH, Moves.ROOST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], [Species.IRON_MOTH]: [ Moves.EARTH_POWER, Moves.SEARING_SHOT, Moves.MALIGNANT_CHAIN, Moves.QUIVER_DANCE ], [Species.IRON_THORNS]: [ Moves.DIAMOND_STORM, Moves.SHORE_UP, Moves.SHIFT_GEAR, Moves.PLASMA_FISTS ], - [Species.FRIGIBAX]: [ Moves.DRAGON_DARTS, Moves.DRAGON_DANCE, Moves.EARTHQUAKE, Moves.GLACIAL_LANCE ], + [Species.FRIGIBAX]: [ Moves.BEHEMOTH_BLADE, Moves.DRAGON_DANCE, Moves.MOUNTAIN_GALE, Moves.PRECIPICE_BLADES ], [Species.GIMMIGHOUL]: [ Moves.HAPPY_HOUR, Moves.AURA_SPHERE, Moves.SURF, Moves.ASTRAL_BARRAGE ], [Species.WO_CHIEN]: [ Moves.SPORE, Moves.FIERY_WRATH, Moves.SAPPY_SEED, Moves.STRENGTH_SAP ], - [Species.CHIEN_PAO]: [ Moves.KNOCK_OFF, Moves.PARTING_SHOT, Moves.BITTER_BLADE, Moves.GLACIAL_LANCE ], - [Species.TING_LU]: [ Moves.SHORE_UP, Moves.WICKED_BLOW, Moves.SAPPY_SEED, Moves.THOUSAND_ARROWS ], + [Species.CHIEN_PAO]: [ Moves.KNOCK_OFF, Moves.PARTING_SHOT, Moves.TRIPLE_AXEL, Moves.BITTER_BLADE ], + [Species.TING_LU]: [ Moves.SHORE_UP, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED, Moves.PRECIPICE_BLADES ], [Species.CHI_YU]: [ Moves.FIERY_WRATH, Moves.HYDRO_STEAM, Moves.MORNING_SUN, Moves.BLUE_FLARE ], [Species.ROARING_MOON]: [ Moves.FIRE_LASH, Moves.DRAGON_HAMMER, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ], [Species.IRON_VALIANT]: [ Moves.PLASMA_FISTS, Moves.NO_RETREAT, Moves.SECRET_SWORD, Moves.MAGICAL_TORQUE ], - [Species.KORAIDON]: [ Moves.SUNSTEEL_STRIKE, Moves.MORNING_SUN, Moves.DRAGON_DARTS, Moves.BITTER_BLADE ], + [Species.KORAIDON]: [ Moves.SUNSTEEL_STRIKE, Moves.SOLAR_BLADE, Moves.DRAGON_DARTS, Moves.BITTER_BLADE ], [Species.MIRAIDON]: [ Moves.ICE_BEAM, Moves.CLANGOROUS_SOUL, Moves.CORE_ENFORCER, Moves.RISING_VOLTAGE ], - [Species.WALKING_WAKE]: [ Moves.CORE_ENFORCER, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.BOUNCY_BUBBLE ], + [Species.WALKING_WAKE]: [ Moves.BOUNCY_BUBBLE, Moves.NASTY_PLOT, Moves.SLUDGE_WAVE, Moves.CORE_ENFORCER ], [Species.IRON_LEAVES]: [ Moves.BITTER_BLADE, Moves.U_TURN, Moves.MIGHTY_CLEAVE, Moves.VICTORY_DANCE ], - [Species.POLTCHAGEIST]: [ Moves.SHELL_SMASH, Moves.BOUNCY_BUBBLE, Moves.LEECH_SEED, Moves.SPARKLY_SWIRL ], - [Species.OKIDOGI]: [ Moves.DRAIN_PUNCH, Moves.KNOCK_OFF, Moves.DIRE_CLAW, Moves.VICTORY_DANCE ], + [Species.POLTCHAGEIST]: [ Moves.PARABOLIC_CHARGE, Moves.BOUNCY_BUBBLE, Moves.LEECH_SEED, Moves.SPARKLY_SWIRL ], + [Species.OKIDOGI]: [ Moves.COMBAT_TORQUE, Moves.TIDY_UP, Moves.DIRE_CLAW, Moves.WICKED_BLOW ], [Species.MUNKIDORI]: [ Moves.PSYSTRIKE, Moves.HEAT_WAVE, Moves.EARTH_POWER, Moves.MALIGNANT_CHAIN ], - [Species.FEZANDIPITI]: [ Moves.BARB_BARRAGE, Moves.VICTORY_DANCE, Moves.TRIPLE_AXEL, Moves.MAGICAL_TORQUE ], - [Species.OGERPON]: [ Moves.FLOWER_TRICK, Moves.BONEMERANG, Moves.TRIPLE_AXEL, Moves.GIGATON_HAMMER ], + [Species.FEZANDIPITI]: [ Moves.BARB_BARRAGE, Moves.BONEMERANG, Moves.TRIPLE_AXEL, Moves.VICTORY_DANCE ], + [Species.OGERPON]: [ Moves.SLEEP_POWDER, Moves.BONEMERANG, Moves.TRIPLE_AXEL, Moves.FLOWER_TRICK ], [Species.GOUGING_FIRE]: [ Moves.EXTREME_SPEED, Moves.BULK_UP, Moves.SACRED_FIRE, Moves.GLAIVE_RUSH ], [Species.RAGING_BOLT]: [ Moves.NASTY_PLOT, Moves.FLAMETHROWER, Moves.MORNING_SUN, Moves.ELECTRO_DRIFT ], [Species.IRON_BOULDER]: [ Moves.PSYBLADE, Moves.KOWTOW_CLEAVE, Moves.STONE_AXE, Moves.BITTER_BLADE ], [Species.IRON_CROWN]: [ Moves.NASTY_PLOT, Moves.SECRET_SWORD, Moves.PSYSTRIKE, Moves.ELECTRO_DRIFT ], [Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.RECOVER, Moves.ICE_BEAM, Moves.SHELL_SMASH ], [Species.PECHARUNT]: [ Moves.TAKE_HEART, Moves.BODY_PRESS, Moves.SAPPY_SEED, Moves.KINGS_SHIELD ], - [Species.ALOLA_RATTATA]: [ Moves.FALSE_SURRENDER, Moves.PSYCHIC_FANGS, Moves.COIL, Moves.EXTREME_SPEED ], - [Species.ALOLA_SANDSHREW]: [ Moves.SPIKY_SHIELD, Moves.AQUA_CUTTER, Moves.SHIFT_GEAR, Moves.GLACIAL_LANCE ], - [Species.ALOLA_VULPIX]: [ Moves.MOONBLAST, Moves.PARTING_SHOT, Moves.FLAMETHROWER, Moves.REVIVAL_BLESSING ], - [Species.ALOLA_DIGLETT]: [ Moves.THOUSAND_WAVES, Moves.SWORDS_DANCE, Moves.TRIPLE_DIVE, Moves.MOUNTAIN_GALE ], - [Species.ALOLA_MEOWTH]: [ Moves.BADDY_BAD, Moves.BUZZY_BUZZ, Moves.PARTING_SHOT, Moves.MAKE_IT_RAIN ], - [Species.ALOLA_GEODUDE]: [ Moves.THOUSAND_WAVES, Moves.BULK_UP, Moves.STONE_AXE, Moves.EXTREME_SPEED ], - [Species.ALOLA_GRIMER]: [ Moves.SUCKER_PUNCH, Moves.BARB_BARRAGE, Moves.STRENGTH_SAP, Moves.SURGING_STRIKES ], - [Species.ETERNAL_FLOETTE]: [ Moves.MIND_BLOWN, Moves.CHLOROBLAST, Moves.PHOTON_GEYSER, Moves.QUIVER_DANCE ], - [Species.GALAR_MEOWTH]: [ Moves.AQUA_CUTTER, Moves.KNOCK_OFF, Moves.BULLET_PUNCH, Moves.BEHEMOTH_BASH ], - [Species.GALAR_PONYTA]: [ Moves.MAGICAL_TORQUE, Moves.EXTREME_SPEED, Moves.FLARE_BLITZ, Moves.PHOTON_GEYSER ], - [Species.GALAR_SLOWPOKE]: [ Moves.TRICK_ROOM, Moves.BADDY_BAD, Moves.MOONBLAST, Moves.TORCH_SONG ], - [Species.GALAR_FARFETCHD]: [ Moves.ROOST, Moves.SACRED_SWORD, Moves.KINGS_SHIELD, Moves.BEHEMOTH_BLADE ], - [Species.GALAR_ARTICUNO]: [ Moves.SECRET_SWORD, Moves.NIGHT_DAZE, Moves.ICE_BEAM, Moves.OBLIVION_WING ], - [Species.GALAR_ZAPDOS]: [ Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.ROOST, Moves.BOLT_BEAK ], - [Species.GALAR_MOLTRES]: [ Moves.ROOST, Moves.SLUDGE_BOMB, Moves.FLAMETHROWER, Moves.OBLIVION_WING ], - [Species.GALAR_CORSOLA]: [ Moves.SHELL_SMASH, Moves.AURA_SPHERE, Moves.INFERNAL_PARADE, Moves.ASTRAL_BARRAGE ], - [Species.GALAR_ZIGZAGOON]: [ Moves.CEASELESS_EDGE, Moves.FACADE, Moves.PARTING_SHOT, Moves.EXTREME_SPEED ], - [Species.GALAR_DARUMAKA]: [ Moves.ICE_SPINNER, Moves.ENDURE, Moves.DRAIN_PUNCH, Moves.V_CREATE ], - [Species.GALAR_YAMASK]: [ Moves.STRENGTH_SAP, Moves.DIRE_CLAW, Moves.THOUSAND_WAVES, Moves.SPECTRAL_THIEF ], - [Species.GALAR_STUNFISK]: [ Moves.SPIKY_SHIELD, Moves.THOUSAND_ARROWS, Moves.STRENGTH_SAP, Moves.DOUBLE_IRON_BASH ], - [Species.HISUI_GROWLITHE]: [ Moves.WAVE_CRASH, Moves.HEAD_SMASH, Moves.VOLT_TACKLE, Moves.DRAGON_DANCE ], - [Species.HISUI_VOLTORB]: [ Moves.FROST_BREATH, Moves.NASTY_PLOT, Moves.APPLE_ACID, Moves.ELECTRO_DRIFT ], - [Species.HISUI_QWILFISH]: [ Moves.CEASELESS_EDGE, Moves.KNOCK_OFF, Moves.STRENGTH_SAP, Moves.FISHIOUS_REND ], - [Species.HISUI_SNEASEL]: [ Moves.THUNDEROUS_KICK, Moves.KNOCK_OFF, Moves.TRIPLE_AXEL, Moves.VICTORY_DANCE ], - [Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.HYPER_VOICE, Moves.PARTING_SHOT, Moves.BLOOD_MOON ], [Species.PALDEA_TAUROS]: [ Moves.NO_RETREAT, Moves.BLAZING_TORQUE, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK ], - [Species.PALDEA_WOOPER]: [ Moves.RECOVER, Moves.STONE_AXE, Moves.BANEFUL_BUNKER, Moves.SAPPY_SEED ], - [Species.BLOODMOON_URSALUNA]: [ Moves.NASTY_PLOT, Moves.TRICK_ROOM, Moves.THUNDERBOLT, Moves.BOOMBURST ] + [Species.PALDEA_WOOPER]: [ Moves.STONE_AXE, Moves.RECOVER, Moves.BANEFUL_BUNKER, Moves.BARB_BARRAGE ], + [Species.BLOODMOON_URSALUNA]: [ Moves.NASTY_PLOT, Moves.ROCK_POLISH, Moves.SANDSEAR_STORM, Moves.BOOMBURST ] }; function parseEggMoves(content: string): void { diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index 6fb0e80e085..3a8285b4586 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -23,9 +23,9 @@ export const starterPassiveAbilities = { [Species.MEOWTH]: Abilities.TOUGH_CLAWS, [Species.PSYDUCK]: Abilities.SIMPLE, [Species.MANKEY]: Abilities.IRON_FIST, - [Species.GROWLITHE]: Abilities.SPEED_BOOST, + [Species.GROWLITHE]: Abilities.FLUFFY, [Species.POLIWAG]: Abilities.NO_GUARD, - [Species.ABRA]: Abilities.PSYCHIC_SURGE, + [Species.ABRA]: Abilities.MAGICIAN, [Species.MACHOP]: Abilities.QUICK_FEET, [Species.BELLSPROUT]: Abilities.FLOWER_GIFT, [Species.TENTACOOL]: Abilities.TOXIC_CHAIN, @@ -41,7 +41,7 @@ export const starterPassiveAbilities = { [Species.GASTLY]: Abilities.SHADOW_SHIELD, [Species.ONIX]: Abilities.ROCKY_PAYLOAD, [Species.DROWZEE]: Abilities.MAGICIAN, - [Species.KRABBY]: Abilities.THERMAL_EXCHANGE, + [Species.KRABBY]: Abilities.UNBURDEN, [Species.VOLTORB]: Abilities.TRANSISTOR, [Species.EXEGGCUTE]: Abilities.RIPEN, [Species.CUBONE]: Abilities.PARENTAL_BOND, @@ -70,6 +70,7 @@ export const starterPassiveAbilities = { [Species.DRATINI]: Abilities.AERILATE, [Species.MEWTWO]: Abilities.NEUROFORCE, [Species.MEW]: Abilities.PROTEAN, + [Species.CHIKORITA]: Abilities.THICK_FAT, [Species.CYNDAQUIL]: Abilities.DROUGHT, [Species.TOTODILE]: Abilities.TOUGH_CLAWS, @@ -77,12 +78,12 @@ export const starterPassiveAbilities = { [Species.HOOTHOOT]: Abilities.AERILATE, [Species.LEDYBA]: Abilities.PRANKSTER, [Species.SPINARAK]: Abilities.PRANKSTER, - [Species.CHINCHOU]: Abilities.WATER_BUBBLE, + [Species.CHINCHOU]: Abilities.REGENERATOR, [Species.PICHU]: Abilities.ELECTRIC_SURGE, [Species.CLEFFA]: Abilities.ANALYTIC, [Species.IGGLYBUFF]: Abilities.HUGE_POWER, [Species.TOGEPI]: Abilities.PIXILATE, - [Species.NATU]: Abilities.TINTED_LENS, + [Species.NATU]: Abilities.SHEER_FORCE, [Species.MAREEP]: Abilities.ELECTROMORPHOSIS, [Species.HOPPIP]: Abilities.FLUFFY, [Species.AIPOM]: Abilities.SCRAPPY, @@ -108,24 +109,25 @@ export const starterPassiveAbilities = { [Species.REMORAID]: Abilities.SIMPLE, [Species.DELIBIRD]: Abilities.HUGE_POWER, [Species.SKARMORY]: Abilities.LIGHTNING_ROD, - [Species.HOUNDOUR]: Abilities.DROUGHT, + [Species.HOUNDOUR]: Abilities.LIGHTNING_ROD, [Species.PHANPY]: Abilities.SPEED_BOOST, [Species.STANTLER]: Abilities.SPEED_BOOST, [Species.SMEARGLE]: Abilities.PRANKSTER, [Species.TYROGUE]: Abilities.MOXIE, [Species.SMOOCHUM]: Abilities.PSYCHIC_SURGE, [Species.ELEKID]: Abilities.SHEER_FORCE, - [Species.MAGBY]: Abilities.CONTRARY, + [Species.MAGBY]: Abilities.SHEER_FORCE, [Species.MILTANK]: Abilities.STAMINA, - [Species.RAIKOU]: Abilities.TRANSISTOR, - [Species.ENTEI]: Abilities.MOXIE, - [Species.SUICUNE]: Abilities.UNAWARE, + [Species.RAIKOU]: Abilities.BEAST_BOOST, + [Species.ENTEI]: Abilities.BEAST_BOOST, + [Species.SUICUNE]: Abilities.BEAST_BOOST, [Species.LARVITAR]: Abilities.SAND_RUSH, [Species.LUGIA]: Abilities.DELTA_STREAM, [Species.HO_OH]: Abilities.MAGIC_GUARD, [Species.CELEBI]: Abilities.PSYCHIC_SURGE, + [Species.TREECKO]: Abilities.TINTED_LENS, - [Species.TORCHIC]: Abilities.RECKLESS, + [Species.TORCHIC]: Abilities.DEFIANT, [Species.MUDKIP]: Abilities.DRIZZLE, [Species.POOCHYENA]: Abilities.TOUGH_CLAWS, [Species.ZIGZAGOON]: Abilities.RUN_AWAY, @@ -148,7 +150,7 @@ export const starterPassiveAbilities = { [Species.MAWILE]: Abilities.UNNERVE, [Species.ARON]: Abilities.EARTH_EATER, [Species.MEDITITE]: Abilities.MINDS_EYE, - [Species.ELECTRIKE]: Abilities.ELECTRIC_SURGE, + [Species.ELECTRIKE]: Abilities.FLASH_FIRE, [Species.PLUSLE]: Abilities.POWER_SPOT, [Species.MINUN]: Abilities.POWER_SPOT, [Species.VOLBEAT]: Abilities.HONEY_GATHER, @@ -162,7 +164,7 @@ export const starterPassiveAbilities = { [Species.SPINDA]: Abilities.SIMPLE, [Species.TRAPINCH]: Abilities.ADAPTABILITY, [Species.CACNEA]: Abilities.SAND_RUSH, - [Species.SWABLU]: Abilities.ADAPTABILITY, + [Species.SWABLU]: Abilities.FLUFFY, [Species.ZANGOOSE]: Abilities.POISON_HEAL, [Species.SEVIPER]: Abilities.MULTISCALE, [Species.LUNATONE]: Abilities.SHADOW_SHIELD, @@ -182,14 +184,14 @@ export const starterPassiveAbilities = { [Species.WYNAUT]: Abilities.STURDY, [Species.SNORUNT]: Abilities.SNOW_WARNING, [Species.SPHEAL]: Abilities.UNAWARE, - [Species.CLAMPERL]: Abilities.DRIZZLE, + [Species.CLAMPERL]: Abilities.ARENA_TRAP, [Species.RELICANTH]: Abilities.PRIMORDIAL_SEA, [Species.LUVDISC]: Abilities.MULTISCALE, - [Species.BAGON]: Abilities.DRAGONS_MAW, + [Species.BAGON]: Abilities.MOLD_BREAKER, [Species.BELDUM]: Abilities.LEVITATE, [Species.REGIROCK]: Abilities.SAND_STREAM, [Species.REGICE]: Abilities.SNOW_WARNING, - [Species.REGISTEEL]: Abilities.FILTER, + [Species.REGISTEEL]: Abilities.STEELY_SPIRIT, [Species.LATIAS]: Abilities.PRISM_ARMOR, [Species.LATIOS]: Abilities.TINTED_LENS, [Species.KYOGRE]: Abilities.MOLD_BREAKER, @@ -197,6 +199,7 @@ export const starterPassiveAbilities = { [Species.RAYQUAZA]: Abilities.UNNERVE, [Species.JIRACHI]: Abilities.COMATOSE, [Species.DEOXYS]: Abilities.PROTEAN, + [Species.TURTWIG]: Abilities.THICK_FAT, [Species.CHIMCHAR]: Abilities.BEAST_BOOST, [Species.PIPLUP]: Abilities.DRIZZLE, @@ -218,9 +221,9 @@ export const starterPassiveAbilities = { [Species.GLAMEOW]: Abilities.INTIMIDATE, [Species.CHINGLING]: Abilities.PUNK_ROCK, [Species.STUNKY]: Abilities.NEUTRALIZING_GAS, - [Species.BRONZOR]: Abilities.BULLETPROOF, + [Species.BRONZOR]: Abilities.MIRROR_ARMOR, [Species.BONSLY]: Abilities.SAP_SIPPER, - [Species.MIME_JR]: Abilities.OPPORTUNIST, + [Species.MIME_JR]: Abilities.PRANKSTER, [Species.HAPPINY]: Abilities.FUR_COAT, [Species.CHATOT]: Abilities.PUNK_ROCK, [Species.SPIRITOMB]: Abilities.VESSEL_OF_RUIN, @@ -233,7 +236,7 @@ export const starterPassiveAbilities = { [Species.CARNIVINE]: Abilities.ARENA_TRAP, [Species.FINNEON]: Abilities.WATER_BUBBLE, [Species.MANTYKE]: Abilities.UNAWARE, - [Species.SNOVER]: Abilities.THICK_FAT, + [Species.SNOVER]: Abilities.GRASSY_SURGE, [Species.ROTOM]: Abilities.HADRON_ENGINE, [Species.UXIE]: Abilities.UNAWARE, [Species.MESPRIT]: Abilities.MOODY, @@ -249,6 +252,7 @@ export const starterPassiveAbilities = { [Species.DARKRAI]: Abilities.UNNERVE, [Species.SHAYMIN]: Abilities.WIND_RIDER, [Species.ARCEUS]: Abilities.ADAPTABILITY, + [Species.VICTINI]: Abilities.SHEER_FORCE, [Species.SNIVY]: Abilities.MULTISCALE, [Species.TEPIG]: Abilities.ROCK_HEAD, @@ -264,7 +268,7 @@ export const starterPassiveAbilities = { [Species.BLITZLE]: Abilities.ELECTRIC_SURGE, [Species.ROGGENROLA]: Abilities.SOLID_ROCK, [Species.WOOBAT]: Abilities.OPPORTUNIST, - [Species.DRILBUR]: Abilities.SAND_STREAM, + [Species.DRILBUR]: Abilities.STURDY, [Species.AUDINO]: Abilities.FRIEND_GUARD, [Species.TIMBURR]: Abilities.ROCKY_PAYLOAD, [Species.TYMPOLE]: Abilities.POISON_HEAL, @@ -292,7 +296,7 @@ export const starterPassiveAbilities = { [Species.DUCKLETT]: Abilities.DRIZZLE, [Species.VANILLITE]: Abilities.SLUSH_RUSH, [Species.DEERLING]: Abilities.FUR_COAT, - [Species.EMOLGA]: Abilities.TRANSISTOR, + [Species.EMOLGA]: Abilities.SERENE_GRACE, [Species.KARRABLAST]: Abilities.QUICK_DRAW, [Species.FOONGUS]: Abilities.THICK_FAT, [Species.FRILLISH]: Abilities.POISON_HEAL, @@ -301,10 +305,10 @@ export const starterPassiveAbilities = { [Species.FERROSEED]: Abilities.ROUGH_SKIN, [Species.KLINK]: Abilities.STEELY_SPIRIT, [Species.TYNAMO]: Abilities.POISON_HEAL, - [Species.ELGYEM]: Abilities.PRISM_ARMOR, - [Species.LITWICK]: Abilities.SOUL_HEART, + [Species.ELGYEM]: Abilities.BEADS_OF_RUIN, + [Species.LITWICK]: Abilities.SHADOW_TAG, [Species.AXEW]: Abilities.DRAGONS_MAW, - [Species.CUBCHOO]: Abilities.TOUGH_CLAWS, + [Species.CUBCHOO]: Abilities.FUR_COAT, [Species.CRYOGONAL]: Abilities.SNOW_WARNING, [Species.SHELMET]: Abilities.PROTEAN, [Species.STUNFISK]: Abilities.STORM_DRAIN, @@ -331,6 +335,7 @@ export const starterPassiveAbilities = { [Species.KELDEO]: Abilities.GRIM_NEIGH, [Species.MELOETTA]: Abilities.MINDS_EYE, [Species.GENESECT]: Abilities.PROTEAN, + [Species.CHESPIN]: Abilities.DAUNTLESS_SHIELD, [Species.FENNEKIN]: Abilities.PSYCHIC_SURGE, [Species.FROAKIE]: Abilities.STAKEOUT, @@ -345,11 +350,11 @@ export const starterPassiveAbilities = { [Species.ESPURR]: Abilities.FUR_COAT, [Species.HONEDGE]: Abilities.SHARPNESS, [Species.SPRITZEE]: Abilities.FUR_COAT, - [Species.SWIRLIX]: Abilities.WELL_BAKED_BODY, + [Species.SWIRLIX]: Abilities.RIPEN, [Species.INKAY]: Abilities.UNNERVE, [Species.BINACLE]: Abilities.SAP_SIPPER, [Species.SKRELP]: Abilities.DRAGONS_MAW, - [Species.CLAUNCHER]: Abilities.SWIFT_SWIM, + [Species.CLAUNCHER]: Abilities.PROTEAN, [Species.HELIOPTILE]: Abilities.PROTEAN, [Species.TYRUNT]: Abilities.RECKLESS, [Species.AMAURA]: Abilities.ICE_SCALES, @@ -364,10 +369,12 @@ export const starterPassiveAbilities = { [Species.NOIBAT]: Abilities.PUNK_ROCK, [Species.XERNEAS]: Abilities.HARVEST, [Species.YVELTAL]: Abilities.SOUL_HEART, - [Species.ZYGARDE]: Abilities.HUGE_POWER, - [Species.DIANCIE]: Abilities.LEVITATE, + [Species.ZYGARDE]: Abilities.ADAPTABILITY, + [Species.DIANCIE]: Abilities.PRISM_ARMOR, [Species.HOOPA]: Abilities.OPPORTUNIST, [Species.VOLCANION]: Abilities.FILTER, + [Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD, + [Species.ROWLET]: Abilities.SNIPER, [Species.LITTEN]: Abilities.OPPORTUNIST, [Species.POPPLIO]: Abilities.PUNK_ROCK, @@ -403,7 +410,7 @@ export const starterPassiveAbilities = { [Species.DRAMPA]: Abilities.THICK_FAT, [Species.DHELMISE]: Abilities.WATER_BUBBLE, [Species.JANGMO_O]: Abilities.DAUNTLESS_SHIELD, - [Species.TAPU_KOKO]: Abilities.TRANSISTOR, + [Species.TAPU_KOKO]: Abilities.DAUNTLESS_SHIELD, [Species.TAPU_LELE]: Abilities.SHEER_FORCE, [Species.TAPU_BULU]: Abilities.TRIAGE, [Species.TAPU_FINI]: Abilities.FAIRY_AURA, @@ -413,16 +420,24 @@ export const starterPassiveAbilities = { [Species.PHEROMOSA]: Abilities.TINTED_LENS, [Species.XURKITREE]: Abilities.TRANSISTOR, [Species.CELESTEELA]: Abilities.HEATPROOF, - [Species.KARTANA]: Abilities.SHARPNESS, + [Species.KARTANA]: Abilities.LONG_REACH, [Species.GUZZLORD]: Abilities.POISON_HEAL, [Species.NECROZMA]: Abilities.BEAST_BOOST, [Species.MAGEARNA]: Abilities.STEELY_SPIRIT, [Species.MARSHADOW]: Abilities.IRON_FIST, - [Species.POIPOLE]: Abilities.SHEER_FORCE, + [Species.POIPOLE]: Abilities.LEVITATE, [Species.STAKATAKA]: Abilities.SOLID_ROCK, [Species.BLACEPHALON]: Abilities.MAGIC_GUARD, [Species.ZERAORA]: Abilities.TOUGH_CLAWS, - [Species.MELTAN]: Abilities.STEELY_SPIRIT, + [Species.MELTAN]: Abilities.HEATPROOF, + [Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY, + [Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES, + [Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE, + [Species.ALOLA_DIGLETT]: Abilities.STURDY, + [Species.ALOLA_MEOWTH]: Abilities.DARK_AURA, + [Species.ALOLA_GEODUDE]: Abilities.DRY_SKIN, + [Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS, + [Species.GROOKEY]: Abilities.GRASS_PELT, [Species.SCORBUNNY]: Abilities.NO_GUARD, [Species.SOBBLE]: Abilities.SUPER_LUCK, @@ -431,7 +446,7 @@ export const starterPassiveAbilities = { [Species.BLIPBUG]: Abilities.PSYCHIC_SURGE, [Species.NICKIT]: Abilities.MAGICIAN, [Species.GOSSIFLEUR]: Abilities.GRASSY_SURGE, - [Species.WOOLOO]: Abilities.SIMPLE, + [Species.WOOLOO]: Abilities.SCRAPPY, [Species.CHEWTLE]: Abilities.ROCKY_PAYLOAD, [Species.YAMPER]: Abilities.SHEER_FORCE, [Species.ROLYCOLY]: Abilities.SOLID_ROCK, @@ -444,7 +459,7 @@ export const starterPassiveAbilities = { [Species.CLOBBOPUS]: Abilities.WATER_BUBBLE, [Species.SINISTEA]: Abilities.SHADOW_SHIELD, [Species.HATENNA]: Abilities.FAIRY_AURA, - [Species.IMPIDIMP]: Abilities.FUR_COAT, + [Species.IMPIDIMP]: Abilities.INTIMIDATE, [Species.MILCERY]: Abilities.REGENERATOR, [Species.FALINKS]: Abilities.PARENTAL_BOND, [Species.PINCURCHIN]: Abilities.ELECTROMORPHOSIS, @@ -455,7 +470,7 @@ export const starterPassiveAbilities = { [Species.MORPEKO]: Abilities.MOODY, [Species.CUFANT]: Abilities.EARTH_EATER, [Species.DRACOZOLT]: Abilities.NO_GUARD, - [Species.ARCTOZOLT]: Abilities.TRANSISTOR, + [Species.ARCTOZOLT]: Abilities.WATER_ABSORB, [Species.DRACOVISH]: Abilities.SWIFT_SWIM, [Species.ARCTOVISH]: Abilities.STRONG_JAW, [Species.DURALUDON]: Abilities.STEELWORKER, @@ -471,6 +486,24 @@ export const starterPassiveAbilities = { [Species.SPECTRIER]: Abilities.SHADOW_SHIELD, [Species.CALYREX]: Abilities.HARVEST, [Species.ENAMORUS]: Abilities.FAIRY_AURA, + [Species.GALAR_MEOWTH]: Abilities.UNBURDEN, + [Species.GALAR_PONYTA]: Abilities.CHILLING_NEIGH, + [Species.GALAR_SLOWPOKE]: Abilities.UNAWARE, + [Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD, + [Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE, + [Species.GALAR_ZAPDOS]: Abilities.TOUGH_CLAWS, + [Species.GALAR_MOLTRES]: Abilities.DARK_AURA, + [Species.GALAR_CORSOLA]: Abilities.SHADOW_SHIELD, + [Species.GALAR_ZIGZAGOON]: Abilities.POISON_HEAL, + [Species.GALAR_DARUMAKA]: Abilities.FLASH_FIRE, + [Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN, + [Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP, + [Species.HISUI_GROWLITHE]: Abilities.RECKLESS, + [Species.HISUI_VOLTORB]: Abilities.TRANSISTOR, + [Species.HISUI_QWILFISH]: Abilities.MERCILESS, + [Species.HISUI_SNEASEL]: Abilities.SCRAPPY, + [Species.HISUI_ZORUA]: Abilities.ADAPTABILITY, + [Species.SPRIGATITO]: Abilities.MAGICIAN, [Species.FUECOCO]: Abilities.PUNK_ROCK, [Species.QUAXLY]: Abilities.OPPORTUNIST, @@ -502,40 +535,40 @@ export const starterPassiveAbilities = { [Species.CYCLIZAR]: Abilities.PROTEAN, [Species.ORTHWORM]: Abilities.REGENERATOR, [Species.GLIMMET]: Abilities.LEVITATE, - [Species.GREAVARD]: Abilities.FUR_COAT, + [Species.GREAVARD]: Abilities.UNAWARE, [Species.FLAMIGO]: Abilities.MOXIE, - [Species.CETODDLE]: Abilities.ICE_SCALES, + [Species.CETODDLE]: Abilities.REFRIGERATE, [Species.VELUZA]: Abilities.SUPER_LUCK, - [Species.DONDOZO]: Abilities.PARENTAL_BOND, - [Species.TATSUGIRI]: Abilities.ADAPTABILITY, + [Species.DONDOZO]: Abilities.DRAGONS_MAW, + [Species.TATSUGIRI]: Abilities.FLUFFY, [Species.GREAT_TUSK]: Abilities.INTIMIDATE, [Species.SCREAM_TAIL]: Abilities.UNAWARE, [Species.BRUTE_BONNET]: Abilities.CHLOROPHYLL, [Species.FLUTTER_MANE]: Abilities.DAZZLING, [Species.SLITHER_WING]: Abilities.SCRAPPY, - [Species.SANDY_SHOCKS]: Abilities.EARTH_EATER, + [Species.SANDY_SHOCKS]: Abilities.ELECTRIC_SURGE, [Species.IRON_TREADS]: Abilities.STEELY_SPIRIT, [Species.IRON_BUNDLE]: Abilities.SNOW_WARNING, [Species.IRON_HANDS]: Abilities.IRON_FIST, [Species.IRON_JUGULIS]: Abilities.LIGHTNING_ROD, [Species.IRON_MOTH]: Abilities.LEVITATE, [Species.IRON_THORNS]: Abilities.SAND_STREAM, - [Species.FRIGIBAX]: Abilities.SNOW_WARNING, + [Species.FRIGIBAX]: Abilities.INTIMIDATE, [Species.GIMMIGHOUL]: Abilities.HONEY_GATHER, [Species.WO_CHIEN]: Abilities.VESSEL_OF_RUIN, [Species.CHIEN_PAO]: Abilities.INTIMIDATE, [Species.TING_LU]: Abilities.STAMINA, [Species.CHI_YU]: Abilities.BERSERK, [Species.ROARING_MOON]: Abilities.TOUGH_CLAWS, - [Species.IRON_VALIANT]: Abilities.ADAPTABILITY, + [Species.IRON_VALIANT]: Abilities.NEUROFORCE, [Species.KORAIDON]: Abilities.OPPORTUNIST, [Species.MIRAIDON]: Abilities.OPPORTUNIST, [Species.WALKING_WAKE]: Abilities.BEAST_BOOST, [Species.IRON_LEAVES]: Abilities.SHARPNESS, [Species.POLTCHAGEIST]: Abilities.TRIAGE, - [Species.OKIDOGI]: Abilities.FUR_COAT, - [Species.MUNKIDORI]: Abilities.NEUROFORCE, - [Species.FEZANDIPITI]: Abilities.LEVITATE, + [Species.OKIDOGI]: Abilities.DARK_AURA, + [Species.MUNKIDORI]: Abilities.MAGICIAN, + [Species.FEZANDIPITI]: Abilities.PIXILATE, [Species.OGERPON]: Abilities.OPPORTUNIST, [Species.GOUGING_FIRE]: Abilities.BEAST_BOOST, [Species.RAGING_BOLT]: Abilities.BEAST_BOOST, @@ -543,31 +576,6 @@ export const starterPassiveAbilities = { [Species.IRON_CROWN]: Abilities.SHARPNESS, [Species.TERAPAGOS]: Abilities.SOUL_HEART, [Species.PECHARUNT]: Abilities.TOXIC_CHAIN, - [Species.ALOLA_RATTATA]: Abilities.ADAPTABILITY, - [Species.ALOLA_SANDSHREW]: Abilities.ICE_SCALES, - [Species.ALOLA_VULPIX]: Abilities.SHEER_FORCE, - [Species.ALOLA_DIGLETT]: Abilities.STURDY, - [Species.ALOLA_MEOWTH]: Abilities.DARK_AURA, - [Species.ALOLA_GEODUDE]: Abilities.DRY_SKIN, - [Species.ALOLA_GRIMER]: Abilities.TOXIC_DEBRIS, - [Species.ETERNAL_FLOETTE]: Abilities.MAGIC_GUARD, - [Species.GALAR_MEOWTH]: Abilities.STEELWORKER, - [Species.GALAR_PONYTA]: Abilities.MOXIE, - [Species.GALAR_SLOWPOKE]: Abilities.UNAWARE, - [Species.GALAR_FARFETCHD]: Abilities.INTREPID_SWORD, - [Species.GALAR_ARTICUNO]: Abilities.SERENE_GRACE, - [Species.GALAR_ZAPDOS]: Abilities.TOUGH_CLAWS, - [Species.GALAR_MOLTRES]: Abilities.DARK_AURA, - [Species.GALAR_CORSOLA]: Abilities.SHADOW_SHIELD, - [Species.GALAR_ZIGZAGOON]: Abilities.POISON_HEAL, - [Species.GALAR_DARUMAKA]: Abilities.FLASH_FIRE, - [Species.GALAR_YAMASK]: Abilities.TABLETS_OF_RUIN, - [Species.GALAR_STUNFISK]: Abilities.ARENA_TRAP, - [Species.HISUI_GROWLITHE]: Abilities.RECKLESS, - [Species.HISUI_VOLTORB]: Abilities.TRANSISTOR, - [Species.HISUI_QWILFISH]: Abilities.MERCILESS, - [Species.HISUI_SNEASEL]: Abilities.SCRAPPY, - [Species.HISUI_ZORUA]: Abilities.ADAPTABILITY, [Species.PALDEA_TAUROS]: Abilities.ADAPTABILITY, [Species.PALDEA_WOOPER]: Abilities.THICK_FAT, [Species.BLOODMOON_URSALUNA]: Abilities.BERSERK diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 8f22b288f45..9e86ea7397b 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,10 +1,10 @@ import { Gender } from "#app/data/gender"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import Pokemon from "#app/field/pokemon"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import * as Utils from "#app/utils"; -import { WeatherType } from "#app/data/weather"; -import { Nature } from "#app/data/nature"; +import { WeatherType } from "#enums/weather-type"; +import { Nature } from "#enums/nature"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -1005,8 +1005,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.COSMOEM, 23, null, null) ], [Species.COSMOEM]: [ - new SpeciesEvolution(Species.SOLGALEO, 53, EvolutionItem.SUN_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(Species.LUNALA, 53, EvolutionItem.MOON_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.SOLGALEO, 1, EvolutionItem.SUN_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(Species.LUNALA, 1, EvolutionItem.MOON_FLUTE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.MELTAN]: [ new SpeciesEvolution(Species.MELMETAL, 48, null, null) diff --git a/src/data/balance/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index 71d98fb4fc2..2a3ab431424 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -16,9 +16,9 @@ interface PokemonSpeciesFormLevelMoves { } /** Moves that can only be learned with a memory-mushroom */ -const RELEARN_MOVE = -1; +export const RELEARN_MOVE = -1; /** Moves that can only be learned with an evolve */ -const EVOLVE_MOVE = 0; +export const EVOLVE_MOVE = 0; export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = { [Species.BULBASAUR]: [ diff --git a/src/data/balance/special-species-groups.ts b/src/data/balance/special-species-groups.ts new file mode 100644 index 00000000000..eeba96595a6 --- /dev/null +++ b/src/data/balance/special-species-groups.ts @@ -0,0 +1,46 @@ +import { Species } from "#enums/species"; + +/** + * A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Paradox_Pok%C3%A9mon | Paradox Pokemon}, NOT including the legendaries Miraidon and Koraidon. + */ +export const NON_LEGEND_PARADOX_POKEMON = [ + Species.GREAT_TUSK, + Species.SCREAM_TAIL, + Species.BRUTE_BONNET, + Species.FLUTTER_MANE, + Species.SLITHER_WING, + Species.SANDY_SHOCKS, + Species.ROARING_MOON, + Species.WALKING_WAKE, + Species.GOUGING_FIRE, + Species.RAGING_BOLT, + Species.IRON_TREADS, + Species.IRON_BUNDLE, + Species.IRON_HANDS, + Species.IRON_JUGULIS, + Species.IRON_MOTH, + Species.IRON_THORNS, + Species.IRON_VALIANT, + Species.IRON_LEAVES, + Species.IRON_BOULDER, + Species.IRON_CROWN, +]; + +/** + * A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Ultra_Beast | Ultra Beasts}, NOT including legendaries such as Necrozma or the Cosmog line. + * + * Note that all of these Ultra Beasts are still considered Sub-Legendary. + */ +export const NON_LEGEND_ULTRA_BEASTS = [ + Species.NIHILEGO, + Species.BUZZWOLE, + Species.PHEROMOSA, + Species.XURKITREE, + Species.CELESTEELA, + Species.KARTANA, + Species.GUZZLORD, + Species.POIPOLE, + Species.NAGANADEL, + Species.STAKATAKA, + Species.BLACEPHALON, +]; diff --git a/src/data/balance/species-egg-tiers.ts b/src/data/balance/species-egg-tiers.ts index 27baa18151a..fee48695565 100644 --- a/src/data/balance/species-egg-tiers.ts +++ b/src/data/balance/species-egg-tiers.ts @@ -14,13 +14,10 @@ export const speciesEggTiers = { [Species.RATTATA]: EggTier.COMMON, [Species.SPEAROW]: EggTier.COMMON, [Species.EKANS]: EggTier.COMMON, - [Species.PIKACHU]: EggTier.COMMON, [Species.SANDSHREW]: EggTier.COMMON, [Species.NIDORAN_F]: EggTier.COMMON, [Species.NIDORAN_M]: EggTier.COMMON, - [Species.CLEFAIRY]: EggTier.COMMON, [Species.VULPIX]: EggTier.COMMON, - [Species.JIGGLYPUFF]: EggTier.COMMON, [Species.ZUBAT]: EggTier.COMMON, [Species.ODDISH]: EggTier.COMMON, [Species.PARAS]: EggTier.COMMON, @@ -39,7 +36,7 @@ export const speciesEggTiers = { [Species.PONYTA]: EggTier.COMMON, [Species.SLOWPOKE]: EggTier.COMMON, [Species.MAGNEMITE]: EggTier.RARE, - [Species.FARFETCHD]: EggTier.COMMON, + [Species.FARFETCHD]: EggTier.RARE, [Species.DODUO]: EggTier.COMMON, [Species.SEEL]: EggTier.COMMON, [Species.GRIMER]: EggTier.COMMON, @@ -51,33 +48,25 @@ export const speciesEggTiers = { [Species.VOLTORB]: EggTier.COMMON, [Species.EXEGGCUTE]: EggTier.COMMON, [Species.CUBONE]: EggTier.COMMON, - [Species.HITMONLEE]: EggTier.RARE, - [Species.HITMONCHAN]: EggTier.RARE, - [Species.LICKITUNG]: EggTier.COMMON, + [Species.LICKITUNG]: EggTier.RARE, [Species.KOFFING]: EggTier.COMMON, - [Species.RHYHORN]: EggTier.COMMON, - [Species.CHANSEY]: EggTier.COMMON, + [Species.RHYHORN]: EggTier.RARE, [Species.TANGELA]: EggTier.COMMON, [Species.KANGASKHAN]: EggTier.RARE, [Species.HORSEA]: EggTier.COMMON, [Species.GOLDEEN]: EggTier.COMMON, [Species.STARYU]: EggTier.COMMON, - [Species.MR_MIME]: EggTier.COMMON, [Species.SCYTHER]: EggTier.RARE, - [Species.JYNX]: EggTier.RARE, - [Species.ELECTABUZZ]: EggTier.RARE, - [Species.MAGMAR]: EggTier.RARE, [Species.PINSIR]: EggTier.RARE, [Species.TAUROS]: EggTier.RARE, - [Species.MAGIKARP]: EggTier.RARE, + [Species.MAGIKARP]: EggTier.COMMON, [Species.LAPRAS]: EggTier.RARE, [Species.DITTO]: EggTier.COMMON, [Species.EEVEE]: EggTier.COMMON, [Species.PORYGON]: EggTier.RARE, - [Species.OMANYTE]: EggTier.COMMON, - [Species.KABUTO]: EggTier.COMMON, + [Species.OMANYTE]: EggTier.RARE, + [Species.KABUTO]: EggTier.RARE, [Species.AERODACTYL]: EggTier.RARE, - [Species.SNORLAX]: EggTier.RARE, [Species.ARTICUNO]: EggTier.EPIC, [Species.ZAPDOS]: EggTier.EPIC, [Species.MOLTRES]: EggTier.EPIC, @@ -93,14 +82,12 @@ export const speciesEggTiers = { [Species.LEDYBA]: EggTier.COMMON, [Species.SPINARAK]: EggTier.COMMON, [Species.CHINCHOU]: EggTier.COMMON, - [Species.PICHU]: EggTier.COMMON, + [Species.PICHU]: EggTier.RARE, [Species.CLEFFA]: EggTier.COMMON, [Species.IGGLYBUFF]: EggTier.COMMON, [Species.TOGEPI]: EggTier.COMMON, [Species.NATU]: EggTier.COMMON, [Species.MAREEP]: EggTier.COMMON, - [Species.MARILL]: EggTier.RARE, - [Species.SUDOWOODO]: EggTier.COMMON, [Species.HOPPIP]: EggTier.COMMON, [Species.AIPOM]: EggTier.COMMON, [Species.SUNKERN]: EggTier.COMMON, @@ -109,7 +96,6 @@ export const speciesEggTiers = { [Species.MURKROW]: EggTier.COMMON, [Species.MISDREAVUS]: EggTier.COMMON, [Species.UNOWN]: EggTier.COMMON, - [Species.WOBBUFFET]: EggTier.COMMON, [Species.GIRAFARIG]: EggTier.COMMON, [Species.PINECO]: EggTier.COMMON, [Species.DUNSPARCE]: EggTier.COMMON, @@ -125,7 +111,6 @@ export const speciesEggTiers = { [Species.CORSOLA]: EggTier.COMMON, [Species.REMORAID]: EggTier.COMMON, [Species.DELIBIRD]: EggTier.COMMON, - [Species.MANTINE]: EggTier.COMMON, [Species.SKARMORY]: EggTier.RARE, [Species.HOUNDOUR]: EggTier.COMMON, [Species.PHANPY]: EggTier.COMMON, @@ -145,7 +130,7 @@ export const speciesEggTiers = { [Species.CELEBI]: EggTier.EPIC, [Species.TREECKO]: EggTier.COMMON, - [Species.TORCHIC]: EggTier.RARE, + [Species.TORCHIC]: EggTier.COMMON, [Species.MUDKIP]: EggTier.COMMON, [Species.POOCHYENA]: EggTier.COMMON, [Species.ZIGZAGOON]: EggTier.COMMON, @@ -154,14 +139,14 @@ export const speciesEggTiers = { [Species.SEEDOT]: EggTier.COMMON, [Species.TAILLOW]: EggTier.COMMON, [Species.WINGULL]: EggTier.COMMON, - [Species.RALTS]: EggTier.COMMON, + [Species.RALTS]: EggTier.RARE, [Species.SURSKIT]: EggTier.COMMON, [Species.SHROOMISH]: EggTier.COMMON, [Species.SLAKOTH]: EggTier.RARE, [Species.NINCADA]: EggTier.RARE, [Species.WHISMUR]: EggTier.COMMON, [Species.MAKUHITA]: EggTier.COMMON, - [Species.AZURILL]: EggTier.RARE, + [Species.AZURILL]: EggTier.COMMON, [Species.NOSEPASS]: EggTier.COMMON, [Species.SKITTY]: EggTier.COMMON, [Species.SABLEYE]: EggTier.COMMON, @@ -173,7 +158,6 @@ export const speciesEggTiers = { [Species.MINUN]: EggTier.COMMON, [Species.VOLBEAT]: EggTier.COMMON, [Species.ILLUMISE]: EggTier.COMMON, - [Species.ROSELIA]: EggTier.COMMON, [Species.GULPIN]: EggTier.COMMON, [Species.CARVANHA]: EggTier.COMMON, [Species.WAILMER]: EggTier.COMMON, @@ -191,21 +175,20 @@ export const speciesEggTiers = { [Species.BARBOACH]: EggTier.COMMON, [Species.CORPHISH]: EggTier.COMMON, [Species.BALTOY]: EggTier.COMMON, - [Species.LILEEP]: EggTier.COMMON, - [Species.ANORITH]: EggTier.COMMON, + [Species.LILEEP]: EggTier.RARE, + [Species.ANORITH]: EggTier.RARE, [Species.FEEBAS]: EggTier.RARE, [Species.CASTFORM]: EggTier.COMMON, [Species.KECLEON]: EggTier.COMMON, [Species.SHUPPET]: EggTier.COMMON, [Species.DUSKULL]: EggTier.COMMON, [Species.TROPIUS]: EggTier.COMMON, - [Species.CHIMECHO]: EggTier.COMMON, [Species.ABSOL]: EggTier.RARE, [Species.WYNAUT]: EggTier.COMMON, [Species.SNORUNT]: EggTier.COMMON, [Species.SPHEAL]: EggTier.COMMON, [Species.CLAMPERL]: EggTier.COMMON, - [Species.RELICANTH]: EggTier.COMMON, + [Species.RELICANTH]: EggTier.RARE, [Species.LUVDISC]: EggTier.COMMON, [Species.BAGON]: EggTier.RARE, [Species.BELDUM]: EggTier.RARE, @@ -228,8 +211,8 @@ export const speciesEggTiers = { [Species.KRICKETOT]: EggTier.COMMON, [Species.SHINX]: EggTier.COMMON, [Species.BUDEW]: EggTier.COMMON, - [Species.CRANIDOS]: EggTier.COMMON, - [Species.SHIELDON]: EggTier.COMMON, + [Species.CRANIDOS]: EggTier.RARE, + [Species.SHIELDON]: EggTier.RARE, [Species.BURMY]: EggTier.COMMON, [Species.COMBEE]: EggTier.COMMON, [Species.PACHIRISU]: EggTier.COMMON, @@ -244,12 +227,12 @@ export const speciesEggTiers = { [Species.BRONZOR]: EggTier.COMMON, [Species.BONSLY]: EggTier.COMMON, [Species.MIME_JR]: EggTier.COMMON, - [Species.HAPPINY]: EggTier.COMMON, + [Species.HAPPINY]: EggTier.RARE, [Species.CHATOT]: EggTier.COMMON, [Species.SPIRITOMB]: EggTier.RARE, [Species.GIBLE]: EggTier.RARE, [Species.MUNCHLAX]: EggTier.RARE, - [Species.RIOLU]: EggTier.COMMON, + [Species.RIOLU]: EggTier.RARE, [Species.HIPPOPOTAS]: EggTier.COMMON, [Species.SKORUPI]: EggTier.COMMON, [Species.CROAGUNK]: EggTier.COMMON, @@ -264,10 +247,10 @@ export const speciesEggTiers = { [Species.DIALGA]: EggTier.LEGENDARY, [Species.PALKIA]: EggTier.LEGENDARY, [Species.HEATRAN]: EggTier.EPIC, - [Species.REGIGIGAS]: EggTier.EPIC, + [Species.REGIGIGAS]: EggTier.LEGENDARY, [Species.GIRATINA]: EggTier.LEGENDARY, [Species.CRESSELIA]: EggTier.EPIC, - [Species.PHIONE]: EggTier.RARE, + [Species.PHIONE]: EggTier.EPIC, [Species.MANAPHY]: EggTier.EPIC, [Species.DARKRAI]: EggTier.EPIC, [Species.SHAYMIN]: EggTier.EPIC, @@ -289,7 +272,7 @@ export const speciesEggTiers = { [Species.ROGGENROLA]: EggTier.COMMON, [Species.WOOBAT]: EggTier.COMMON, [Species.DRILBUR]: EggTier.RARE, - [Species.AUDINO]: EggTier.COMMON, + [Species.AUDINO]: EggTier.RARE, [Species.TIMBURR]: EggTier.RARE, [Species.TYMPOLE]: EggTier.COMMON, [Species.THROH]: EggTier.RARE, @@ -306,8 +289,8 @@ export const speciesEggTiers = { [Species.SCRAGGY]: EggTier.COMMON, [Species.SIGILYPH]: EggTier.RARE, [Species.YAMASK]: EggTier.COMMON, - [Species.TIRTOUGA]: EggTier.COMMON, - [Species.ARCHEN]: EggTier.COMMON, + [Species.TIRTOUGA]: EggTier.RARE, + [Species.ARCHEN]: EggTier.RARE, [Species.TRUBBISH]: EggTier.COMMON, [Species.ZORUA]: EggTier.COMMON, [Species.MINCCINO]: EggTier.COMMON, @@ -339,7 +322,7 @@ export const speciesEggTiers = { [Species.BOUFFALANT]: EggTier.RARE, [Species.RUFFLET]: EggTier.COMMON, [Species.VULLABY]: EggTier.COMMON, - [Species.HEATMOR]: EggTier.COMMON, + [Species.HEATMOR]: EggTier.RARE, [Species.DURANT]: EggTier.RARE, [Species.DEINO]: EggTier.RARE, [Species.LARVESTA]: EggTier.RARE, @@ -358,7 +341,7 @@ export const speciesEggTiers = { [Species.CHESPIN]: EggTier.COMMON, [Species.FENNEKIN]: EggTier.COMMON, - [Species.FROAKIE]: EggTier.RARE, + [Species.FROAKIE]: EggTier.COMMON, [Species.BUNNELBY]: EggTier.COMMON, [Species.FLETCHLING]: EggTier.COMMON, [Species.SCATTERBUG]: EggTier.COMMON, @@ -376,8 +359,8 @@ export const speciesEggTiers = { [Species.SKRELP]: EggTier.COMMON, [Species.CLAUNCHER]: EggTier.COMMON, [Species.HELIOPTILE]: EggTier.COMMON, - [Species.TYRUNT]: EggTier.COMMON, - [Species.AMAURA]: EggTier.COMMON, + [Species.TYRUNT]: EggTier.RARE, + [Species.AMAURA]: EggTier.RARE, [Species.HAWLUCHA]: EggTier.RARE, [Species.DEDENNE]: EggTier.COMMON, [Species.CARBINK]: EggTier.COMMON, @@ -386,18 +369,18 @@ export const speciesEggTiers = { [Species.PHANTUMP]: EggTier.COMMON, [Species.PUMPKABOO]: EggTier.COMMON, [Species.BERGMITE]: EggTier.COMMON, - [Species.NOIBAT]: EggTier.COMMON, + [Species.NOIBAT]: EggTier.RARE, [Species.XERNEAS]: EggTier.LEGENDARY, [Species.YVELTAL]: EggTier.LEGENDARY, [Species.ZYGARDE]: EggTier.LEGENDARY, [Species.DIANCIE]: EggTier.EPIC, [Species.HOOPA]: EggTier.EPIC, [Species.VOLCANION]: EggTier.EPIC, - [Species.ETERNAL_FLOETTE]: EggTier.RARE, + [Species.ETERNAL_FLOETTE]: EggTier.EPIC, [Species.ROWLET]: EggTier.COMMON, [Species.LITTEN]: EggTier.COMMON, - [Species.POPPLIO]: EggTier.RARE, + [Species.POPPLIO]: EggTier.COMMON, [Species.PIKIPEK]: EggTier.COMMON, [Species.YUNGOOS]: EggTier.COMMON, [Species.GRUBBIN]: EggTier.COMMON, @@ -420,7 +403,7 @@ export const speciesEggTiers = { [Species.WIMPOD]: EggTier.COMMON, [Species.SANDYGAST]: EggTier.COMMON, [Species.PYUKUMUKU]: EggTier.COMMON, - [Species.TYPE_NULL]: EggTier.RARE, + [Species.TYPE_NULL]: EggTier.EPIC, [Species.MINIOR]: EggTier.RARE, [Species.KOMALA]: EggTier.COMMON, [Species.TURTONATOR]: EggTier.RARE, @@ -434,7 +417,7 @@ export const speciesEggTiers = { [Species.TAPU_LELE]: EggTier.EPIC, [Species.TAPU_BULU]: EggTier.EPIC, [Species.TAPU_FINI]: EggTier.EPIC, - [Species.COSMOG]: EggTier.EPIC, + [Species.COSMOG]: EggTier.LEGENDARY, [Species.NIHILEGO]: EggTier.EPIC, [Species.BUZZWOLE]: EggTier.EPIC, [Species.PHEROMOSA]: EggTier.EPIC, @@ -451,15 +434,15 @@ export const speciesEggTiers = { [Species.ZERAORA]: EggTier.EPIC, [Species.MELTAN]: EggTier.EPIC, [Species.ALOLA_RATTATA]: EggTier.COMMON, - [Species.ALOLA_SANDSHREW]: EggTier.COMMON, - [Species.ALOLA_VULPIX]: EggTier.COMMON, - [Species.ALOLA_DIGLETT]: EggTier.COMMON, - [Species.ALOLA_MEOWTH]: EggTier.COMMON, - [Species.ALOLA_GEODUDE]: EggTier.COMMON, - [Species.ALOLA_GRIMER]: EggTier.COMMON, + [Species.ALOLA_SANDSHREW]: EggTier.RARE, + [Species.ALOLA_VULPIX]: EggTier.RARE, + [Species.ALOLA_DIGLETT]: EggTier.RARE, + [Species.ALOLA_MEOWTH]: EggTier.RARE, + [Species.ALOLA_GEODUDE]: EggTier.RARE, + [Species.ALOLA_GRIMER]: EggTier.RARE, [Species.GROOKEY]: EggTier.COMMON, - [Species.SCORBUNNY]: EggTier.RARE, + [Species.SCORBUNNY]: EggTier.COMMON, [Species.SOBBLE]: EggTier.COMMON, [Species.SKWOVET]: EggTier.COMMON, [Species.ROOKIDEE]: EggTier.COMMON, @@ -505,29 +488,28 @@ export const speciesEggTiers = { [Species.GLASTRIER]: EggTier.EPIC, [Species.SPECTRIER]: EggTier.EPIC, [Species.CALYREX]: EggTier.LEGENDARY, - [Species.GALAR_MEOWTH]: EggTier.COMMON, - [Species.GALAR_PONYTA]: EggTier.COMMON, - [Species.GALAR_SLOWPOKE]: EggTier.COMMON, - [Species.GALAR_FARFETCHD]: EggTier.COMMON, - [Species.GALAR_CORSOLA]: EggTier.COMMON, - [Species.GALAR_ZIGZAGOON]: EggTier.COMMON, - [Species.GALAR_DARUMAKA]: EggTier.RARE, - [Species.GALAR_YAMASK]: EggTier.COMMON, - [Species.GALAR_STUNFISK]: EggTier.COMMON, - [Species.GALAR_MR_MIME]: EggTier.COMMON, + [Species.ENAMORUS]: EggTier.EPIC, + [Species.GALAR_MEOWTH]: EggTier.RARE, + [Species.GALAR_PONYTA]: EggTier.RARE, + [Species.GALAR_SLOWPOKE]: EggTier.RARE, + [Species.GALAR_FARFETCHD]: EggTier.RARE, [Species.GALAR_ARTICUNO]: EggTier.EPIC, [Species.GALAR_ZAPDOS]: EggTier.EPIC, [Species.GALAR_MOLTRES]: EggTier.EPIC, + [Species.GALAR_CORSOLA]: EggTier.RARE, + [Species.GALAR_ZIGZAGOON]: EggTier.RARE, + [Species.GALAR_DARUMAKA]: EggTier.RARE, + [Species.GALAR_YAMASK]: EggTier.RARE, + [Species.GALAR_STUNFISK]: EggTier.RARE, [Species.HISUI_GROWLITHE]: EggTier.RARE, - [Species.HISUI_VOLTORB]: EggTier.COMMON, + [Species.HISUI_VOLTORB]: EggTier.RARE, [Species.HISUI_QWILFISH]: EggTier.RARE, [Species.HISUI_SNEASEL]: EggTier.RARE, - [Species.HISUI_ZORUA]: EggTier.COMMON, - [Species.ENAMORUS]: EggTier.EPIC, + [Species.HISUI_ZORUA]: EggTier.RARE, - [Species.SPRIGATITO]: EggTier.RARE, - [Species.FUECOCO]: EggTier.RARE, - [Species.QUAXLY]: EggTier.RARE, + [Species.SPRIGATITO]: EggTier.COMMON, + [Species.FUECOCO]: EggTier.COMMON, + [Species.QUAXLY]: EggTier.COMMON, [Species.LECHONK]: EggTier.COMMON, [Species.TAROUNTULA]: EggTier.COMMON, [Species.NYMBLE]: EggTier.COMMON, @@ -551,7 +533,7 @@ export const speciesEggTiers = { [Species.TINKATINK]: EggTier.RARE, [Species.WIGLETT]: EggTier.COMMON, [Species.BOMBIRDIER]: EggTier.COMMON, - [Species.FINIZEN]: EggTier.COMMON, + [Species.FINIZEN]: EggTier.RARE, [Species.VAROOM]: EggTier.RARE, [Species.CYCLIZAR]: EggTier.RARE, [Species.ORTHWORM]: EggTier.RARE, @@ -598,6 +580,6 @@ export const speciesEggTiers = { [Species.TERAPAGOS]: EggTier.LEGENDARY, [Species.PECHARUNT]: EggTier.EPIC, [Species.PALDEA_TAUROS]: EggTier.RARE, - [Species.PALDEA_WOOPER]: EggTier.COMMON, - [Species.BLOODMOON_URSALUNA]: EggTier.EPIC, + [Species.PALDEA_WOOPER]: EggTier.RARE, + [Species.BLOODMOON_URSALUNA]: EggTier.EPIC }; diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index d6a1f0c3eaf..ec66401675b 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -3,10 +3,10 @@ import { Species } from "#enums/species"; export const POKERUS_STARTER_COUNT = 5; // #region Friendship constants -export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2; -export const FRIENDSHIP_GAIN_FROM_BATTLE = 2; -export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5; -export const FRIENDSHIP_LOSS_FROM_FAINT = 10; +export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 3; +export const FRIENDSHIP_GAIN_FROM_BATTLE = 3; +export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 6; +export const FRIENDSHIP_LOSS_FROM_FAINT = 5; /** * Function to get the cumulative friendship threshold at which a candy is earned @@ -16,19 +16,19 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10; export function getStarterValueFriendshipCap(starterCost: number): number { switch (starterCost) { case 1: - return 20; + return 25; case 2: - return 40; + return 50; case 3: - return 60; + return 75; case 4: return 100; case 5: - return 140; + return 150; case 6: return 200; case 7: - return 280; + return 300; case 8: case 9: return 450; @@ -47,7 +47,7 @@ export const speciesStarterCosts = { [Species.RATTATA]: 1, [Species.SPEAROW]: 1, [Species.EKANS]: 2, - [Species.PIKACHU]: 3, + [Species.PIKACHU]: 4, [Species.SANDSHREW]: 2, [Species.NIDORAN_F]: 3, [Species.NIDORAN_M]: 3, @@ -88,7 +88,7 @@ export const speciesStarterCosts = { [Species.HITMONCHAN]: 4, [Species.LICKITUNG]: 3, [Species.KOFFING]: 2, - [Species.RHYHORN]: 3, + [Species.RHYHORN]: 4, [Species.CHANSEY]: 3, [Species.TANGELA]: 3, [Species.KANGASKHAN]: 4, @@ -111,12 +111,12 @@ export const speciesStarterCosts = { [Species.KABUTO]: 3, [Species.AERODACTYL]: 5, [Species.SNORLAX]: 5, - [Species.ARTICUNO]: 6, + [Species.ARTICUNO]: 5, [Species.ZAPDOS]: 6, [Species.MOLTRES]: 6, [Species.DRATINI]: 4, [Species.MEWTWO]: 8, - [Species.MEW]: 6, + [Species.MEW]: 5, [Species.CHIKORITA]: 2, [Species.CYNDAQUIL]: 3, @@ -126,7 +126,7 @@ export const speciesStarterCosts = { [Species.LEDYBA]: 1, [Species.SPINARAK]: 1, [Species.CHINCHOU]: 2, - [Species.PICHU]: 2, + [Species.PICHU]: 4, [Species.CLEFFA]: 2, [Species.IGGLYBUFF]: 1, [Species.TOGEPI]: 3, @@ -175,7 +175,7 @@ export const speciesStarterCosts = { [Species.LARVITAR]: 4, [Species.LUGIA]: 8, [Species.HO_OH]: 8, - [Species.CELEBI]: 6, + [Species.CELEBI]: 5, [Species.TREECKO]: 3, [Species.TORCHIC]: 4, @@ -187,7 +187,7 @@ export const speciesStarterCosts = { [Species.SEEDOT]: 2, [Species.TAILLOW]: 3, [Species.WINGULL]: 2, - [Species.RALTS]: 3, + [Species.RALTS]: 4, [Species.SURSKIT]: 2, [Species.SHROOMISH]: 3, [Species.SLAKOTH]: 4, @@ -198,7 +198,7 @@ export const speciesStarterCosts = { [Species.NOSEPASS]: 2, [Species.SKITTY]: 1, [Species.SABLEYE]: 2, - [Species.MAWILE]: 3, + [Species.MAWILE]: 2, [Species.ARON]: 3, [Species.MEDITITE]: 3, [Species.ELECTRIKE]: 2, @@ -243,7 +243,7 @@ export const speciesStarterCosts = { [Species.BAGON]: 4, [Species.BELDUM]: 4, [Species.REGIROCK]: 6, - [Species.REGICE]: 6, + [Species.REGICE]: 5, [Species.REGISTEEL]: 6, [Species.LATIAS]: 7, [Species.LATIOS]: 7, @@ -291,19 +291,19 @@ export const speciesStarterCosts = { [Species.MANTYKE]: 2, [Species.SNOVER]: 2, [Species.ROTOM]: 5, - [Species.UXIE]: 6, - [Species.MESPRIT]: 6, + [Species.UXIE]: 5, + [Species.MESPRIT]: 5, [Species.AZELF]: 6, [Species.DIALGA]: 8, [Species.PALKIA]: 8, - [Species.HEATRAN]: 6, + [Species.HEATRAN]: 7, [Species.REGIGIGAS]: 7, [Species.GIRATINA]: 8, [Species.CRESSELIA]: 6, [Species.PHIONE]: 4, [Species.MANAPHY]: 7, [Species.DARKRAI]: 7, - [Species.SHAYMIN]: 6, + [Species.SHAYMIN]: 5, [Species.ARCEUS]: 9, [Species.VICTINI]: 7, @@ -351,7 +351,7 @@ export const speciesStarterCosts = { [Species.DEERLING]: 2, [Species.EMOLGA]: 2, [Species.KARRABLAST]: 3, - [Species.FOONGUS]: 2, + [Species.FOONGUS]: 3, [Species.FRILLISH]: 3, [Species.ALOMOMOLA]: 4, [Species.JOLTIK]: 3, @@ -410,7 +410,7 @@ export const speciesStarterCosts = { [Species.CLAUNCHER]: 3, [Species.HELIOPTILE]: 3, [Species.TYRUNT]: 3, - [Species.AMAURA]: 3, + [Species.AMAURA]: 2, [Species.HAWLUCHA]: 4, [Species.DEDENNE]: 2, [Species.CARBINK]: 2, @@ -425,7 +425,7 @@ export const speciesStarterCosts = { [Species.ZYGARDE]: 8, [Species.DIANCIE]: 7, [Species.HOOPA]: 7, - [Species.VOLCANION]: 6, + [Species.VOLCANION]: 7, [Species.ETERNAL_FLOETTE]: 4, [Species.ROWLET]: 3, @@ -464,21 +464,21 @@ export const speciesStarterCosts = { [Species.DHELMISE]: 4, [Species.JANGMO_O]: 4, [Species.TAPU_KOKO]: 6, - [Species.TAPU_LELE]: 6, + [Species.TAPU_LELE]: 7, [Species.TAPU_BULU]: 6, - [Species.TAPU_FINI]: 6, + [Species.TAPU_FINI]: 5, [Species.COSMOG]: 7, [Species.NIHILEGO]: 6, [Species.BUZZWOLE]: 6, [Species.PHEROMOSA]: 7, [Species.XURKITREE]: 6, [Species.CELESTEELA]: 6, - [Species.KARTANA]: 7, + [Species.KARTANA]: 8, [Species.GUZZLORD]: 6, [Species.NECROZMA]: 8, [Species.MAGEARNA]: 7, [Species.MARSHADOW]: 7, - [Species.POIPOLE]: 7, + [Species.POIPOLE]: 8, [Species.STAKATAKA]: 6, [Species.BLACEPHALON]: 7, [Species.ZERAORA]: 6, @@ -532,31 +532,31 @@ export const speciesStarterCosts = { [Species.ZAMAZENTA]: 8, [Species.ETERNATUS]: 10, [Species.KUBFU]: 6, - [Species.ZARUDE]: 6, + [Species.ZARUDE]: 5, [Species.REGIELEKI]: 6, [Species.REGIDRAGO]: 6, [Species.GLASTRIER]: 6, - [Species.SPECTRIER]: 7, + [Species.SPECTRIER]: 8, [Species.CALYREX]: 8, + [Species.ENAMORUS]: 7, [Species.GALAR_MEOWTH]: 3, [Species.GALAR_PONYTA]: 2, [Species.GALAR_SLOWPOKE]: 3, [Species.GALAR_FARFETCHD]: 3, + [Species.GALAR_MR_MIME]: 3, + [Species.GALAR_ARTICUNO]: 6, + [Species.GALAR_ZAPDOS]: 6, + [Species.GALAR_MOLTRES]: 6, [Species.GALAR_CORSOLA]: 3, [Species.GALAR_ZIGZAGOON]: 3, [Species.GALAR_DARUMAKA]: 4, [Species.GALAR_YAMASK]: 3, [Species.GALAR_STUNFISK]: 2, - [Species.GALAR_MR_MIME]: 3, - [Species.GALAR_ARTICUNO]: 6, - [Species.GALAR_ZAPDOS]: 6, - [Species.GALAR_MOLTRES]: 6, [Species.HISUI_GROWLITHE]: 4, [Species.HISUI_VOLTORB]: 3, [Species.HISUI_QWILFISH]: 4, [Species.HISUI_SNEASEL]: 5, [Species.HISUI_ZORUA]: 3, - [Species.ENAMORUS]: 7, [Species.SPRIGATITO]: 4, [Species.FUECOCO]: 4, @@ -595,9 +595,9 @@ export const speciesStarterCosts = { [Species.VELUZA]: 4, [Species.DONDOZO]: 4, [Species.TATSUGIRI]: 4, - [Species.GREAT_TUSK]: 6, - [Species.SCREAM_TAIL]: 6, - [Species.BRUTE_BONNET]: 6, + [Species.GREAT_TUSK]: 7, + [Species.SCREAM_TAIL]: 5, + [Species.BRUTE_BONNET]: 5, [Species.FLUTTER_MANE]: 7, [Species.SLITHER_WING]: 6, [Species.SANDY_SHOCKS]: 6, @@ -606,33 +606,33 @@ export const speciesStarterCosts = { [Species.IRON_HANDS]: 6, [Species.IRON_JUGULIS]: 6, [Species.IRON_MOTH]: 6, - [Species.IRON_THORNS]: 6, + [Species.IRON_THORNS]: 5, [Species.FRIGIBAX]: 4, [Species.GIMMIGHOUL]: 4, - [Species.WO_CHIEN]: 6, + [Species.WO_CHIEN]: 5, [Species.CHIEN_PAO]: 7, [Species.TING_LU]: 6, [Species.CHI_YU]: 7, - [Species.ROARING_MOON]: 6, + [Species.ROARING_MOON]: 7, [Species.IRON_VALIANT]: 6, [Species.KORAIDON]: 9, [Species.MIRAIDON]: 9, - [Species.WALKING_WAKE]: 6, + [Species.WALKING_WAKE]: 7, [Species.IRON_LEAVES]: 6, [Species.POLTCHAGEIST]: 4, [Species.OKIDOGI]: 6, [Species.MUNKIDORI]: 6, - [Species.FEZANDIPITI]: 6, + [Species.FEZANDIPITI]: 5, [Species.OGERPON]: 7, [Species.GOUGING_FIRE]: 7, - [Species.RAGING_BOLT]: 6, + [Species.RAGING_BOLT]: 7, [Species.IRON_BOULDER]: 7, - [Species.IRON_CROWN]: 6, + [Species.IRON_CROWN]: 7, [Species.TERAPAGOS]: 8, [Species.PECHARUNT]: 6, [Species.PALDEA_TAUROS]: 5, [Species.PALDEA_WOOPER]: 3, - [Species.BLOODMOON_URSALUNA]: 6, + [Species.BLOODMOON_URSALUNA]: 5, }; const starterCandyCosts: { passive: number; costReduction: [number, number]; egg: number; }[] = [ diff --git a/src/data/balance/tms.ts b/src/data/balance/tms.ts index 7b65ae65ec4..4882cf4f652 100644 --- a/src/data/balance/tms.ts +++ b/src/data/balance/tms.ts @@ -3302,11 +3302,23 @@ export const tmSpecies: TmSpecies = { Species.FERALIGATR, Species.SENTRET, Species.FURRET, + Species.HOOTHOOT, Species.NOCTOWL, + Species.LEDYBA, + Species.LEDIAN, + Species.SPINARAK, + Species.ARIADOS, Species.CROBAT, Species.CHINCHOU, Species.LANTURN, + Species.PICHU, + Species.CLEFFA, Species.IGGLYBUFF, + Species.TYROGUE, + Species.TOGEPI, + Species.TOGETIC, + Species.NATU, + Species.XATU, Species.MAREEP, Species.FLAAFFY, Species.AMPHAROS, @@ -3328,6 +3340,7 @@ export const tmSpecies: TmSpecies = { Species.UMBREON, Species.MURKROW, Species.SLOWKING, + Species.MISDREAVUS, Species.GIRAFARIG, Species.PINECO, Species.FORRETRESS, @@ -3338,11 +3351,21 @@ export const tmSpecies: TmSpecies = { Species.GRANBULL, Species.QWILFISH, Species.SCIZOR, + Species.SHUCKLE, Species.HERACROSS, + Species.SNEASEL, Species.TEDDIURSA, Species.URSARING, + Species.SLUGMA, + Species.MAGCARGO, Species.SWINUB, Species.PILOSWINE, + Species.CORSOLA, + Species.REMORAID, + Species.OCTILLERY, + Species.DELIBIRD, + Species.MANTINE, + Species.SKARMORY, Species.HOUNDOUR, Species.HOUNDOOM, Species.KINGDRA, @@ -3350,9 +3373,12 @@ export const tmSpecies: TmSpecies = { Species.DONPHAN, Species.PORYGON2, Species.STANTLER, + Species.TYROGUE, Species.HITMONTOP, + Species.SMOOCHUM, Species.ELEKID, Species.MAGBY, + Species.MILTANK, Species.BLISSEY, Species.RAIKOU, Species.ENTEI, @@ -3362,6 +3388,9 @@ export const tmSpecies: TmSpecies = { Species.TYRANITAR, Species.LUGIA, Species.HO_OH, + Species.CELEBI, + Species.TREECKO, + Species.GROVYLE, Species.SCEPTILE, Species.TORCHIC, Species.COMBUSKEN, @@ -3371,41 +3400,116 @@ export const tmSpecies: TmSpecies = { Species.SWAMPERT, Species.POOCHYENA, Species.MIGHTYENA, + Species.ZIGZAGOON, + Species.LINOONE, + Species.BEAUTIFLY, + Species.DUSTOX, Species.LOTAD, Species.LOMBRE, Species.LUDICOLO, Species.SEEDOT, Species.NUZLEAF, Species.SHIFTRY, + Species.TAILLOW, + Species.SWELLOW, + Species.WINGULL, + Species.PELIPPER, + Species.RALTS, + Species.KIRLIA, + Species.GARDEVOIR, + Species.SURSKIT, + Species.MASQUERAIN, + Species.SHROOMISH, + Species.BRELOOM, Species.VIGOROTH, Species.SLAKING, + Species.NINCADA, + Species.NINJASK, + Species.SHEDINJA, + Species.WHISMUR, + Species.LOUDRED, + Species.EXPLOUD, Species.MAKUHITA, Species.HARIYAMA, + Species.AZURILL, Species.NOSEPASS, + Species.SKITTY, + Species.DELCATTY, + Species.SABLEYE, + Species.MAWILE, + Species.ARON, + Species.LAIRON, + Species.AGGRON, + Species.MEDITITE, + Species.MEDICHAM, + Species.ELECTRIKE, + Species.MANECTRIC, + Species.PLUSLE, + Species.MINUN, Species.VOLBEAT, Species.ILLUMISE, + Species.ROSELIA, + Species.GULPIN, Species.SWALOT, + Species.CARVANHA, + Species.SHARPEDO, + Species.WAILMER, + Species.WAILORD, Species.NUMEL, Species.CAMERUPT, Species.TORKOAL, + Species.SPOINK, + Species.GRUMPIG, + Species.SPINDA, + Species.TRAPINCH, + Species.VIBRAVA, Species.FLYGON, + Species.CACNEA, + Species.CACTURNE, + Species.SWABLU, Species.ALTARIA, Species.ZANGOOSE, Species.SEVIPER, + Species.LUNATONE, + Species.SOLROCK, Species.BARBOACH, Species.WHISCASH, Species.CORPHISH, Species.CRAWDAUNT, + Species.BALTOY, + Species.CLAYDOL, + Species.LILEEP, + Species.CRADILY, + Species.ANORITH, + Species.ARMALDO, Species.FEEBAS, Species.MILOTIC, + Species.CASTFORM, + Species.KECLEON, + Species.SHUPPET, + Species.BANETTE, + Species.DUSKULL, + Species.DUSCLOPS, Species.TROPIUS, Species.CHIMECHO, + Species.ABSOL, + Species.SNORUNT, + Species.GLALIE, + Species.SPHEAL, + Species.SEALEO, + Species.WALREIN, + Species.CLAMPERL, + Species.HUNTAIL, + Species.GOREBYSS, + Species.RELICANTH, + Species.LUVDISC, Species.BAGON, Species.SHELGON, Species.SALAMENCE, Species.METANG, Species.METAGROSS, Species.REGIROCK, + Species.REGICE, Species.REGISTEEL, Species.LATIAS, Species.LATIOS, @@ -3413,6 +3517,7 @@ export const tmSpecies: TmSpecies = { Species.GROUDON, Species.RAYQUAZA, Species.JIRACHI, + Species.DEOXYS, Species.TURTWIG, Species.GROTLE, Species.TORTERRA, @@ -64246,12 +64351,16 @@ export const tmSpecies: TmSpecies = { Species.BLOODMOON_URSALUNA, ], [Moves.LIQUIDATION]: [ + Species.SQUIRTLE, + Species.WARTORTLE, Species.BLASTOISE, Species.PSYDUCK, Species.GOLDUCK, Species.POLIWAG, Species.POLIWHIRL, Species.POLIWRATH, + Species.TENTACOOL, + Species.TENTACRUEL, Species.SLOWPOKE, Species.SLOWBRO, Species.DEWGONG, @@ -64267,7 +64376,11 @@ export const tmSpecies: TmSpecies = { Species.KABUTO, Species.KABUTOPS, Species.MEW, + Species.TOTODILE, + Species.CROCONAW, Species.FERALIGATR, + Species.CHINCHOU, + Species.LANTURN, Species.MARILL, Species.AZUMARILL, Species.POLITOED, @@ -64280,6 +64393,9 @@ export const tmSpecies: TmSpecies = { Species.MANTINE, Species.KINGDRA, Species.SUICUNE, + Species.LUGIA, + Species.MUDKIP, + Species.MARSHTOMP, Species.SWAMPERT, Species.WINGULL, Species.PELIPPER, @@ -64296,6 +64412,8 @@ export const tmSpecies: TmSpecies = { Species.WALREIN, Species.RELICANTH, Species.LUVDISC, + Species.LATIAS, + Species.LATIOS, Species.KYOGRE, Species.PIPLUP, Species.PRINPLUP, @@ -64407,11 +64525,13 @@ export const tmSpecies: TmSpecies = { Species.ONIX, Species.HYPNO, Species.LICKITUNG, + Species.RHYHORN, Species.RHYDON, Species.LAPRAS, Species.SNORLAX, Species.DRAGONITE, Species.MEW, + Species.MEGANIUM, Species.SUDOWOODO, Species.QUAGSIRE, Species.FORRETRESS, @@ -64445,6 +64565,8 @@ export const tmSpecies: TmSpecies = { Species.REGISTEEL, Species.GROUDON, Species.TORTERRA, + Species.RAMPARDOS, + Species.BASTIODON, Species.BRONZONG, Species.HIPPOPOTAS, Species.HIPPOWDON, @@ -64459,6 +64581,7 @@ export const tmSpecies: TmSpecies = { Species.HEATRAN, Species.REGIGIGAS, Species.ARCEUS, + Species.EMBOAR, Species.ROGGENROLA, Species.BOLDORE, Species.GIGALITH, @@ -64471,6 +64594,7 @@ export const tmSpecies: TmSpecies = { Species.CUBCHOO, Species.BEARTIC, Species.GOLURK, + Species.COBALION, Species.RESHIRAM, Species.ZEKROM, Species.KYUREM, diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 5928a65b4e3..26ad65bd9b0 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -90,6 +90,7 @@ export enum CommonAnim { RAGING_BULL_FIRE, RAGING_BULL_WATER, SALT_CURE, + POWDER, SUNNY = 2100, RAIN, SANDSTORM, diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 15bc745ec9d..0c0b8e9e034 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -18,10 +18,9 @@ import Move, { StatusCategoryOnAllyAttr } from "#app/data/move"; import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; -import { StatusEffect } from "#app/data/status-effect"; +import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; -import { Type } from "#app/data/type"; -import { WeatherType } from "#app/data/weather"; +import { Type } from "#enums/type"; import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; @@ -38,6 +37,8 @@ import { Moves } from "#enums/moves"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { Species } from "#enums/species"; import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; export enum BattlerTagLapseType { FAINT, @@ -855,6 +856,59 @@ export class SeedTag extends BattlerTag { } } +/** + * BattlerTag representing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Powder_(move) | Powder}. + * When the afflicted Pokemon uses a Fire-type move, the move is cancelled, and the + * Pokemon takes damage equal to 1/4 of it's maximum HP (rounded down). + */ +export class PowderTag extends BattlerTag { + constructor() { + super(BattlerTagType.POWDER, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], 1); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + // "{Pokemon} is covered in powder!" + pokemon.scene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + /** + * Applies Powder's effects before the tag owner uses a Fire-type move. + * Also causes the tag to expire at the end of turn. + * @param pokemon {@linkcode Pokemon} the owner of this tag + * @param lapseType {@linkcode BattlerTagLapseType} the type of lapse functionality to carry out + * @returns `true` if the tag should not expire after this lapse; `false` otherwise. + */ + lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + if (lapseType === BattlerTagLapseType.PRE_MOVE) { + const movePhase = pokemon.scene.getCurrentPhase(); + if (movePhase instanceof MovePhase) { + const move = movePhase.move.getMove(); + const weather = pokemon.scene.arena.weather; + if (pokemon.getMoveType(move) === Type.FIRE && !(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed(pokemon.scene))) { + movePhase.fail(); + movePhase.showMoveText(); + + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER)); + + const cancelDamage = new BooleanHolder(false); + applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); + if (!cancelDamage.value) { + pokemon.damageAndUpdate(Math.floor(pokemon.getMaxHp() / 4), HitResult.OTHER); + } + + // "When the flame touched the powder\non the Pokémon, it exploded!" + pokemon.scene.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); + } + } + return true; + } else { + return super.lapse(pokemon, lapseType); + } + } +} + export class NightmareTag extends BattlerTag { constructor() { super(BattlerTagType.NIGHTMARE, BattlerTagLapseType.TURN_END, 1, Moves.NIGHTMARE); @@ -909,11 +963,15 @@ export class FrenzyTag extends BattlerTag { } } -export class EncoreTag extends BattlerTag { +/** + * Applies the effects of the move Encore onto the target Pokemon + * Encore forces the target Pokemon to use its most-recent move for 3 turns + */ +export class EncoreTag extends MoveRestrictionBattlerTag { public moveId: Moves; constructor(sourceId: number) { - super(BattlerTagType.ENCORE, BattlerTagLapseType.AFTER_MOVE, 3, Moves.ENCORE, sourceId); + super(BattlerTagType.ENCORE, [ BattlerTagLapseType.CUSTOM, BattlerTagLapseType.AFTER_MOVE ], 3, Moves.ENCORE, sourceId); } /** @@ -969,6 +1027,39 @@ export class EncoreTag extends BattlerTag { } } + /** + * If the encored move has run out of PP, Encore ends early. Otherwise, Encore lapses based on the AFTER_MOVE battler tag lapse type. + * @returns `true` to persist | `false` to end and be removed + */ + override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + if (lapseType === BattlerTagLapseType.CUSTOM) { + const encoredMove = pokemon.getMoveset().find(m => m?.moveId === this.moveId); + if (encoredMove && encoredMove?.getPpRatio() > 0) { + return true; + } + return false; + } else { + return super.lapse(pokemon, lapseType); + } + } + + /** + * Checks if the move matches the moveId stored within the tag and returns a boolean value + * @param move {@linkcode Moves} the move selected + * @param user N/A + * @returns `true` if the move does not match with the moveId stored and as a result, restricted + */ + override isMoveRestricted(move: Moves, _user?: Pokemon): boolean { + if (move !== this.moveId) { + return true; + } + return false; + } + + override selectionDeniedText(_pokemon: Pokemon, move: Moves): string { + return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name }); + } + onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); @@ -1047,10 +1138,6 @@ export class OctolockTag extends TrappedTag { super(BattlerTagType.OCTOLOCK, BattlerTagLapseType.TURN_END, 1, Moves.OCTOLOCK, sourceId); } - canAdd(pokemon: Pokemon): boolean { - return !pokemon.getTag(BattlerTagType.OCTOLOCK); - } - lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); @@ -1464,9 +1551,14 @@ export class ContactBurnProtectedTag extends DamageProtectedTag { } } +/** + * `BattlerTag` class for effects that cause the affected Pokemon to survive lethal attacks at 1 HP. + * Used for {@link https://bulbapedia.bulbagarden.net/wiki/Endure_(move) | Endure} and + * Endure Tokens. + */ export class EnduringTag extends BattlerTag { - constructor(sourceMove: Moves) { - super(BattlerTagType.ENDURING, BattlerTagLapseType.TURN_END, 0, sourceMove); + constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, sourceMove: Moves) { + super(tagType, lapseType, 0, sourceMove); } onAdd(pokemon: Pokemon): void { @@ -1771,8 +1863,8 @@ export class TypeImmuneTag extends BattlerTag { * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} */ export class FloatingTag extends TypeImmuneTag { - constructor(tagType: BattlerTagType, sourceMove: Moves) { - super(tagType, sourceMove, Type.GROUND, 5); + constructor(tagType: BattlerTagType, sourceMove: Moves, turnCount: number) { + super(tagType, sourceMove, Type.GROUND, turnCount); } onAdd(pokemon: Pokemon): void { @@ -2091,6 +2183,42 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag { } } +/** + * Battler tag indicating a Tatsugiri with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} + * has entered the tagged Pokemon's mouth. + */ +export class CommandedTag extends BattlerTag { + private _tatsugiriFormKey: string; + + constructor(sourceId: number) { + super(BattlerTagType.COMMANDED, BattlerTagLapseType.CUSTOM, 0, Moves.NONE, sourceId); + } + + public get tatsugiriFormKey(): string { + return this._tatsugiriFormKey; + } + + /** Caches the Tatsugiri's form key and sharply boosts the tagged Pokemon's stats */ + override onAdd(pokemon: Pokemon): void { + this._tatsugiriFormKey = this.getSourcePokemon(pokemon.scene)?.getFormKey() ?? "curly"; + pokemon.scene.unshiftPhase(new StatStageChangePhase( + pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2 + )); + } + + /** Triggers an {@linkcode PokemonAnimType | animation} of the tagged Pokemon "spitting out" Tatsugiri */ + override onRemove(pokemon: Pokemon): void { + if (this.getSourcePokemon(pokemon.scene)?.isActive(true)) { + pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_REMOVE); + } + } + + override loadTag(source: BattlerTag | any): void { + super.loadTag(source); + this._tatsugiriFormKey = source._tatsugiriFormKey; + } +} + /** * Battler tag enabling the Stockpile mechanic. This tag handles: * - Stack tracking, including max limit enforcement (which is replicated in Stockpile for redundancy). @@ -2329,7 +2457,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { } /** - * Uses DisabledTag's selectionDeniedText() message + * Uses its own unique selectionDeniedText() message */ override selectionDeniedText(pokemon: Pokemon, move: Moves): string { return i18next.t("battle:moveDisabledHealBlock", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name, healBlockName: allMoves[Moves.HEAL_BLOCK].name }); @@ -2796,6 +2924,67 @@ export class PowerTrickTag extends BattlerTag { } } +/** + * Tag associated with the move Grudge. + * If this tag is active when the bearer faints from an opponent's move, the tag reduces that move's PP to 0. + * Otherwise, it lapses when the bearer makes another move. + */ +export class GrudgeTag extends BattlerTag { + constructor() { + super(BattlerTagType.GRUDGE, [ BattlerTagLapseType.CUSTOM, BattlerTagLapseType.PRE_MOVE ], 1, Moves.GRUDGE); + } + + onAdd(pokemon: Pokemon) { + super.onAdd(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:grudgeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + /** + * Activates Grudge's special effect on the attacking Pokemon and lapses the tag. + * @param pokemon + * @param lapseType + * @param sourcePokemon {@linkcode Pokemon} the source of the move that fainted the tag's bearer + * @returns `false` if Grudge activates its effect or lapses + */ + override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType, sourcePokemon?: Pokemon): boolean { + if (lapseType === BattlerTagLapseType.CUSTOM && sourcePokemon) { + if (sourcePokemon.isActive() && pokemon.isOpponent(sourcePokemon)) { + const lastMove = pokemon.turnData.attacksReceived[0]; + const lastMoveData = sourcePokemon.getMoveset().find(m => m?.moveId === lastMove.move); + if (lastMoveData && lastMove.move !== Moves.STRUGGLE) { + lastMoveData.ppUsed = lastMoveData.getMovePp(); + pokemon.scene.queueMessage(i18next.t("battlerTags:grudgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: lastMoveData.getName() })); + } + } + return false; + } else { + return super.lapse(pokemon, lapseType); + } + } +} + +/** + * Tag used to heal the user of Psycho Shift of its status effect if Psycho Shift succeeds in transferring its status effect to the target Pokemon + */ +export class PsychoShiftTag extends BattlerTag { + constructor() { + super(BattlerTagType.PSYCHO_SHIFT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.PSYCHO_SHIFT); + } + + /** + * Heals Psycho Shift's user of its status effect after it uses a move + * @returns `false` to expire the tag immediately + */ + override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { + if (pokemon.status && pokemon.isActive(true)) { + pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + pokemon.resetStatus(); + pokemon.updateInfo(); + } + return false; + } +} + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * @param sourceId - The ID of the pokemon adding the tag @@ -2819,6 +3008,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new InfatuatedTag(sourceMove, sourceId); case BattlerTagType.SEEDED: return new SeedTag(sourceId); + case BattlerTagType.POWDER: + return new PowderTag(); case BattlerTagType.NIGHTMARE: return new NightmareTag(); case BattlerTagType.FRENZY: @@ -2874,7 +3065,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source case BattlerTagType.BURNING_BULWARK: return new ContactBurnProtectedTag(sourceMove); case BattlerTagType.ENDURING: - return new EnduringTag(sourceMove); + return new EnduringTag(tagType, BattlerTagLapseType.TURN_END, sourceMove); + case BattlerTagType.ENDURE_TOKEN: + return new EnduringTag(tagType, BattlerTagLapseType.AFTER_HIT, sourceMove); case BattlerTagType.STURDY: return new SturdyTag(sourceMove); case BattlerTagType.PERISH_SONG: @@ -2923,7 +3116,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source case BattlerTagType.CHARGED: return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); case BattlerTagType.FLOATING: - return new FloatingTag(tagType, sourceMove); + return new FloatingTag(tagType, sourceMove, turnCount); case BattlerTagType.MINIMIZED: return new MinimizeTag(); case BattlerTagType.DESTINY_BOND: @@ -2932,6 +3125,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new IceFaceBlockDamageTag(tagType); case BattlerTagType.DISGUISE: return new FormBlockDamageTag(tagType); + case BattlerTagType.COMMANDED: + return new CommandedTag(sourceId); case BattlerTagType.STOCKPILING: return new StockpilingTag(sourceMove); case BattlerTagType.OCTOLOCK: @@ -2975,6 +3170,10 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new TelekinesisTag(sourceMove); case BattlerTagType.POWER_TRICK: return new PowerTrickTag(sourceMove, sourceId); + case BattlerTagType.GRUDGE: + return new GrudgeTag(); + case BattlerTagType.PSYCHO_SHIFT: + return new PsychoShiftTag(); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/berry.ts b/src/data/berry.ts index d2bbd0fdd1c..dfd6a7ddcf0 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -61,13 +61,13 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate { } } -export type BerryEffectFunc = (pokemon: Pokemon) => void; +export type BerryEffectFunc = (pokemon: Pokemon, berryOwner?: Pokemon) => void; export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { switch (berryType) { case BerryType.SITRUS: case BerryType.ENIGMA: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } @@ -75,10 +75,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LUM: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } @@ -87,14 +87,14 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { } pokemon.resetStatus(true, true); pokemon.updateInfo(); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LIECHI: case BerryType.GANLON: case BerryType.PETAYA: case BerryType.APICOT: case BerryType.SALAC: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } @@ -103,18 +103,18 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const statStages = new Utils.NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LANSAT: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } pokemon.addTag(BattlerTagType.CRIT_BOOST); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.STARF: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } @@ -122,10 +122,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const stages = new Utils.NumberHolder(2); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LEPPA: - return (pokemon: Pokemon) => { + return (pokemon: Pokemon, berryOwner?: Pokemon) => { if (pokemon.battleData) { pokemon.battleData.berriesEaten.push(berryType); } @@ -133,7 +133,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { if (ppRestoreMove !== undefined) { ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); - applyPostItemLostAbAttrs(PostItemLostAbAttr, pokemon, false); + applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); } }; } diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 27b6be851bc..f19cf17eab5 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -7,11 +7,11 @@ import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { BattleType, FixedBattleConfig } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import { GameMode } from "#app/game-mode"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { Moves } from "#enums/moves"; import { TypeColor, TypeShadow } from "#enums/color"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; @@ -838,7 +838,7 @@ export class FreshStartChallenge extends Challenge { pokemon.shiny = false; // Not shiny pokemon.variant = 0; // Not shiny pokemon.formIndex = 0; // Froakie should be base form - pokemon.ivs = [ 10, 10, 10, 10, 10, 10 ]; // Default IVs of 10 for all stats + pokemon.ivs = [ 15, 15, 15, 15, 15, 15 ]; // Default IVs of 15 for all stats (Updated to 15 from 10 in 1.2.0) return true; } diff --git a/src/data/custom-pokemon-data.ts b/src/data/custom-pokemon-data.ts index 2e94123fc84..7bc884cff50 100644 --- a/src/data/custom-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -1,5 +1,5 @@ import { Abilities } from "#enums/abilities"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { isNullOrUndefined } from "#app/utils"; import { Nature } from "#enums/nature"; diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 0decab63f4f..506ea0471c6 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -6,6 +6,7 @@ import { Starter } from "#app/ui/starter-select-ui-handler"; import * as Utils from "#app/utils"; import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; export interface DailyRunConfig { seed: integer; @@ -14,14 +15,9 @@ export interface DailyRunConfig { export function fetchDailyRunSeed(): Promise { return new Promise((resolve, reject) => { - Utils.apiFetch("daily/seed").then(response => { - if (!response.ok) { - resolve(null); - return; - } - return response.text(); - }).then(seed => resolve(seed ?? null)) - .catch(err => reject(err)); + pokerogueApi.daily.getSeed().then(dailySeed => { + resolve(dailySeed); + }); }); } diff --git a/src/data/egg.ts b/src/data/egg.ts index f7d109dfa62..7f1deecc63f 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -17,39 +17,44 @@ export const EGG_SEED = 1073741824; /** Egg options to override egg properties */ export interface IEggOptions { - /** Id. Used to check if egg type will be manaphy (id % 204 === 0) */ + /** ID. Used to check if egg type will be manaphy (`id % 204 === 0`) */ id?: number; /** Timestamp when this egg got created */ timestamp?: number; - /** Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applyed. + /** + * Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applyed. * Egg will be automaticly added to the game data. - * NEEDS scene eggOption to work. + * NEEDS `scene` `eggOption` to work. */ pulled?: boolean; - /** Defines where the egg comes from. Applies specific modifiers. + /** + * Defines where the egg comes from. Applies specific modifiers. * Will also define the text displayed in the egg list. */ sourceType?: EggSourceType; - /** Needs to be defined if eggOption pulled is defined or if no species or isShiny is degined since this will be needed to generate them. */ + /** Needs to be defined if `eggOption` pulled is defined or if no species or `isShiny` is defined since this will be needed to generate them. */ scene?: BattleScene; - /** Sets the tier of the egg. Only species of this tier can be hatched from this egg. - * Tier will be overriden if species eggOption is set. + /** + * Sets the tier of the egg. Only species of this tier can be hatched from this egg. + * Tier will be overriden if species `eggOption` is set. */ tier?: EggTier; /** Sets how many waves it will take till this egg hatches. */ hatchWaves?: number; - /** Sets the exact species that will hatch from this egg. - * Needs scene eggOption if not provided. + /** + * Sets the exact species that will hatch from this egg. + * Needs `scene` `eggOption` if not provided. */ species?: Species; /** Defines if the hatched pokemon will be a shiny. */ isShiny?: boolean; - /** Defines the variant of the pokemon that will hatch from this egg. If no variantTier is given the normal variant rates will apply. */ + /** Defines the variant of the pokemon that will hatch from this egg. If no `variantTier` is given the normal variant rates will apply. */ variantTier?: VariantTier; - /** Defines which egg move will be unlocked. 3 = rare egg move. */ + /** Defines which egg move will be unlocked. `3` = rare egg move. */ eggMoveIndex?: number; - /** Defines if the egg will hatch with the hidden ability of this species. - * If no hidden ability exist, a random one will get choosen. + /** + * Defines if the egg will hatch with the hidden ability of this species. + * If no hidden ability exist, a random one will get choosen. */ overrideHiddenAbility?: boolean, @@ -418,7 +423,7 @@ export class Egg { } /** - * Pokemon that are cheaper in their tier get a weight boost. Regionals get a weight penalty + * Pokemon that are cheaper in their tier get a weight boost. * 1 cost mons get 2x * 2 cost mons get 1.5x * 4, 6, 8 cost mons get 1.75x @@ -433,11 +438,7 @@ export class Egg { for (const speciesId of speciesPool) { // Accounts for species that have starter costs outside of the normal range for their EggTier const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue); - let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); - const species = getPokemonSpecies(speciesId); - if (species.isRegional()) { - weight = Math.floor(weight / 2); - } + const weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); speciesWeights.push(totalWeight + weight); totalWeight += weight; } diff --git a/src/data/move.ts b/src/data/move.ts index 6e350315e65..7a6f08a5372 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,14 +1,15 @@ import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims"; -import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; +import { CommandedTag, EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; -import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect, StatusEffect } from "./status-effect"; -import { getTypeDamageMultiplier, Type } from "./type"; +import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect } from "./status-effect"; +import { getTypeDamageMultiplier } from "./type"; +import { Type } from "#enums/type"; import { Constructor, NumberHolder } from "#app/utils"; import * as Utils from "../utils"; -import { WeatherType } from "./weather"; +import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; -import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; +import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ChangeMovePriorityAbAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; import { BattlerIndex, BattleType } from "../battle"; import { TerrainType } from "./terrain"; @@ -38,6 +39,7 @@ import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; import { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "./challenge"; import { SwitchType } from "#enums/switch-type"; +import { StatusEffect } from "enums/status-effect"; export enum MoveCategory { PHYSICAL, @@ -666,12 +668,12 @@ export default class Move implements Localizable { } /** - * Sees if, given the target pokemon, a move fails on it (by looking at each {@linkcode MoveAttr} of this move + * Sees if a move has a custom failure text (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 + * @returns string of the custom failure text, or `null` if it uses the default text ("But it failed!") */ getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null { for (const attr of this.attrs) { @@ -714,6 +716,10 @@ export default class Move implements Localizable { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { let score = 0; + if (target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(target.scene) === target) { + return 20 * (target.isPlayer() === user.isPlayer() ? -1 : 1); // always -20 with how the AI handles this score + } + 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); @@ -812,8 +818,6 @@ export default class Move implements Localizable { 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, simulated, this.type, power); source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); @@ -825,6 +829,54 @@ export default class Move implements Localizable { return power.value; } + + getPriority(user: Pokemon, simulated: boolean = true) { + const priority = new Utils.NumberHolder(this.priority); + + applyMoveAttrs(IncrementMovePriorityAttr, user, null, this, priority); + applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); + + return priority.value; + } + + /** + * Returns `true` if this move can be given additional strikes + * by enhancing effects. + * Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond} + * and {@linkcode PokemonMultiHitModifier | Multi-Lens}. + * @param user The {@linkcode Pokemon} using the move + * @param restrictSpread `true` if the enhancing effect + * should not affect multi-target moves (default `false`) + */ + canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false): boolean { + // Multi-strike enhancers... + + // ...cannot enhance moves that hit multiple targets + const { targets, multiple } = getMoveTargets(user, this.id); + const isMultiTarget = multiple && targets.length > 1; + + // ...cannot enhance multi-hit or sacrificial moves + const exceptAttrs: Constructor[] = [ + MultiHitAttr, + SacrificialAttr, + SacrificialAttrOnHit + ]; + + // ...and cannot enhance these specific moves. + const exceptMoves: Moves[] = [ + Moves.FLING, + Moves.UPROAR, + Moves.ROLLOUT, + Moves.ICE_BALL, + Moves.ENDEAVOR + ]; + + return (!restrictSpread || !isMultiTarget) + && !this.isChargingMove() + && !exceptAttrs.some(attr => this.hasAttr(attr)) + && !exceptMoves.some(id => this.id === id) + && this.category !== MoveCategory.STATUS; + } } export class AttackMove extends Move { @@ -845,7 +897,7 @@ export class AttackMove extends Move { let attackScore = 0; - const effectiveness = target.getAttackTypeEffectiveness(this.type, user); + const effectiveness = target.getAttackTypeEffectiveness(this.type, user, undefined, undefined, this); attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2; if (attackScore) { if (this.category === MoveCategory.PHYSICAL) { @@ -1333,14 +1385,38 @@ export class UserHpDamageAttr extends FixedDamageAttr { } export class TargetHalfHpDamageAttr extends FixedDamageAttr { + // the initial amount of hp the target had before the first hit + // used for multi lens + private initialHp: number; constructor() { super(0); } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value = Utils.toDmgValue(target.hp / 2); + // first, determine if the hit is coming from multi lens or not + const lensCount = user.getHeldItems().find(i => i instanceof PokemonMultiHitModifier)?.getStackCount() ?? 0; + if (lensCount <= 0) { + // no multi lenses; we can just halve the target's hp and call it a day + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + return true; + } - return true; + // figure out what hit # we're on + switch (user.turnData.hitCount - user.turnData.hitsLeft) { + case 0: + // first hit of move; update initialHp tracker + this.initialHp = target.hp; + default: + // multi lens added hit; use initialHp tracker to ensure correct damage + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(this.initialHp / 2); + return true; + break; + case lensCount + 1: + // parental bond added hit; calc damage as normal + (args[0] as Utils.NumberHolder).value = Utils.toDmgValue(target.hp / 2); + return true; + break; + } } getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { @@ -1791,7 +1867,7 @@ export class FlameBurstAttr extends MoveEffectAttr { applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled); } - if (cancelled.value || !targetAlly) { + if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) { return false; } @@ -1805,8 +1881,14 @@ export class FlameBurstAttr extends MoveEffectAttr { } export class SacrificialFullRestoreAttr extends SacrificialAttr { - constructor() { + protected restorePP: boolean; + protected moveMessage: string; + + constructor(restorePP: boolean, moveMessage: string) { super(); + + this.restorePP = restorePP; + this.moveMessage = moveMessage; } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { @@ -1817,8 +1899,19 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { // We don't know which party member will be chosen, so pick the highest max HP in the party const maxPartyMemberHp = user.scene.getPlayerParty().map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0); - user.scene.pushPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), - maxPartyMemberHp, i18next.t("moveTriggers:sacrificialFullRestore", { pokemonName: getPokemonNameWithAffix(user) }), true, false, false, true), true); + user.scene.pushPhase( + new PokemonHealPhase( + user.scene, + user.getBattlerIndex(), + maxPartyMemberHp, + i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }), + true, + false, + false, + true, + false, + this.restorePP), + true); return true; } @@ -2266,24 +2359,26 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr { super(false, { trigger: MoveEffectTrigger.HIT }); } - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + /** + * Applies the effect of Psycho Shift to its target + * Psycho Shift takes the user's status effect and passes it onto the target. The user is then healed after the move has been successfully executed. + * @returns `true` if Psycho Shift's effect is able to be applied to the target + */ + apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { const statusToApply: StatusEffect | undefined = user.status?.effect ?? (user.hasAbility(Abilities.COMATOSE) ? StatusEffect.SLEEP : undefined); if (target.status) { return false; } else { const canSetStatus = target.canSetStatus(statusToApply, true, false, user); + const trySetStatus = canSetStatus ? target.trySetStatus(statusToApply, true, user) : false; - if (canSetStatus) { - if (user.status) { - user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user))); - } - user.resetStatus(); - user.updateInfo(); - target.trySetStatus(statusToApply, true, user); + if (trySetStatus && user.status) { + // PsychoShiftTag is added to the user if move succeeds so that the user is healed of its status effect after its move + user.addTag(BattlerTagType.PSYCHO_SHIFT); } - return canSetStatus; + return trySetStatus; } } @@ -2400,9 +2495,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { const removedItem = heldItems[user.randSeedInt(heldItems.length)]; // Decrease item amount and update icon - !--removedItem.stackCount; + target.loseHeldItem(removedItem); target.scene.updateModifiers(target.isPlayer()); - applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); if (this.berriesOnly) { @@ -2472,18 +2566,15 @@ export class EatBerryAttr extends MoveEffectAttr { } reduceBerryModifier(target: Pokemon) { - if (this.chosenBerry?.stackCount === 1) { - target.scene.removeModifier(this.chosenBerry, !target.isPlayer()); - } else if (this.chosenBerry !== undefined && this.chosenBerry.stackCount > 1) { - this.chosenBerry.stackCount--; + if (this.chosenBerry) { + target.loseHeldItem(this.chosenBerry); } target.scene.updateModifiers(target.isPlayer()); } - eatBerry(consumer: Pokemon) { - getBerryEffectFunc(this.chosenBerry!.berryType)(consumer); // consumer eats the berry + eatBerry(consumer: Pokemon, berryOwner?: Pokemon) { + getBerryEffectFunc(this.chosenBerry!.berryType)(consumer, berryOwner); // consumer eats the berry applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false)); - applyPostItemLostAbAttrs(PostItemLostAbAttr, consumer, false); } } @@ -2523,7 +2614,7 @@ export class StealEatBerryAttr extends EatBerryAttr { 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); + this.eatBerry(user, target); return true; } } @@ -2539,12 +2630,11 @@ export class HealStatusEffectAttr extends MoveEffectAttr { /** * @param selfTarget - Whether this move targets the user - * @param ...effects - List of status effects to cure + * @param effects - status effect or list of status effects to cure */ - constructor(selfTarget: boolean, ...effects: StatusEffect[]) { + constructor(selfTarget: boolean, effects: StatusEffect | StatusEffect[]) { super(selfTarget, { lastHitOnly: true }); - - this.effects = effects; + this.effects = [ effects ].flat(1); } /** @@ -2785,6 +2875,14 @@ export class OverrideMoveEffectAttr extends MoveAttr { } } +/** + * Attack Move that doesn't hit the turn it is played and doesn't allow for multiple + * uses on the same target. Examples are Future Sight or Doom Desire. + * @extends OverrideMoveEffectAttr + * @param tagType The {@linkcode ArenaTagType} that will be placed on the field when the move is used + * @param chargeAnim The {@linkcode ChargeAnim | Charging Animation} used for the move + * @param chargeText The text to display when the move is used + */ export class DelayedAttackAttr extends OverrideMoveEffectAttr { public tagType: ArenaTagType; public chargeAnim: ChargeAnim; @@ -2799,13 +2897,18 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + // Edge case for the move applied on a pokemon that has fainted + if (!target) { + return Promise.resolve(true); + } + const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; return new Promise(resolve => { if (args.length < 2 || !args[1]) { new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, false, () => { (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()); + user.scene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); resolve(true); }); @@ -3232,6 +3335,41 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr { } } +/** + * Attribute implementing the stat boosting effect of {@link https://bulbapedia.bulbagarden.net/wiki/Order_Up_(move) | Order Up}. + * If the user has a Pokemon with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} in their mouth, + * one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form. This effect does not respect + * effect chance, but Order Up itself may be boosted by Sheer Force. + */ +export class OrderUpStatBoostAttr extends MoveEffectAttr { + constructor() { + super(true, { trigger: MoveEffectTrigger.HIT }); + } + + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + const commandedTag = user.getTag(CommandedTag); + if (!commandedTag) { + return false; + } + + let increasedStat: EffectiveStat = Stat.ATK; + switch (commandedTag.tatsugiriFormKey) { + case "curly": + increasedStat = Stat.ATK; + break; + case "droopy": + increasedStat = Stat.DEF; + break; + case "stretchy": + increasedStat = Stat.SPD; + break; + } + + user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); + return true; + } +} + export class CopyStatsAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!super.apply(user, target, move, args)) { @@ -4873,22 +5011,6 @@ export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTy } } -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)) { - const effectivenessAgainstWater = new Utils.NumberHolder(getTypeDamageMultiplier(move.type, Type.WATER)); - applyChallenges(user.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, effectivenessAgainstWater); - if (effectivenessAgainstWater.value !== 0) { - multiplier.value *= 2 / effectivenessAgainstWater.value; - 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. @@ -4916,6 +5038,41 @@ export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr { } } +/** + * Attribute for moves which have a custom type chart interaction. + */ +export class VariableMoveTypeChartAttr extends MoveAttr { + /** + * @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 NumberHolder} holding the type effectiveness + * @param args [1] A single defensive type of the target + * + * @returns true if application of the attribute succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + return false; + } +} + +/** + * This class forces Freeze-Dry to be super effective against Water Type. + */ +export class FreezeDryAttr extends VariableMoveTypeChartAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const multiplier = args[0] as Utils.NumberHolder; + const defType = args[1] as Type; + + if (defType === Type.WATER) { + multiplier.value = 2; + return true; + } else { + return false; + } + } +} + export class OneHitKOAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const accuracy = args[0] as Utils.NumberHolder; @@ -5534,7 +5691,8 @@ export class AddArenaTagAttr extends MoveEffectAttr { } if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { - user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); return true; } @@ -5838,56 +5996,125 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return false; } + /** The {@linkcode Pokemon} to be switched out with this effect */ const switchOutTarget = this.selfSwitch ? user : target; + + // If the switch-out target is a Dondozo with a Tatsugiri in its mouth + // (e.g. when it uses Flip Turn), make it spit out the Tatsugiri before switching out. + switchOutTarget.lapseTag(BattlerTagType.COMMANDED); + if (switchOutTarget instanceof PlayerPokemon) { /** * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * If it did, the user of U-turn or Volt Switch will not be switched out. */ - if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) && - (move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH || move.id === Moves.FLIP_TURN) + if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) + && [ Moves.U_TURN, Moves.VOLT_SWITCH, Moves.FLIP_TURN ].includes(move.id) ) { if (this.hpDroppedBelowHalf(target)) { return false; } } - // Switch out logic for the player's Pokemon - if (switchOutTarget.scene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + + // Find indices of off-field Pokemon that are eligible to be switched into + const eligibleNewIndices: number[] = []; + switchOutTarget.scene.getPlayerParty().forEach((pokemon, index) => { + if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) { + eligibleNewIndices.push(index); + } + }); + + if (eligibleNewIndices.length < 1) { return false; } if (switchOutTarget.hp > 0) { - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); - return true; + if (this.switchType === SwitchType.FORCE_SWITCH) { + switchOutTarget.leaveField(true); + const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)]; + user.scene.prependToPhase( + new SwitchSummonPhase( + user.scene, + this.switchType, + switchOutTarget.getFieldIndex(), + slotIndex, + false, + true + ), + MoveEndPhase + ); + } else { + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); + user.scene.prependToPhase( + new SwitchPhase( + user.scene, + this.switchType, + switchOutTarget.getFieldIndex(), + true, + true + ), + MoveEndPhase + ); + return true; + } } return false; - } else if (user.scene.currentBattle.battleType !== BattleType.WILD) { - // Switch out logic for trainer battles - if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + } else if (user.scene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers + // Find indices of off-field Pokemon that are eligible to be switched into + const eligibleNewIndices: number[] = []; + switchOutTarget.scene.getEnemyParty().forEach((pokemon, index) => { + if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) { + eligibleNewIndices.push(index); + } + }); + + if (eligibleNewIndices.length < 1) { return false; } if (switchOutTarget.hp > 0) { - // for opponent switching out - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), - (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), - false, false), MoveEndPhase); + if (this.switchType === SwitchType.FORCE_SWITCH) { + switchOutTarget.leaveField(true); + const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)]; + user.scene.prependToPhase( + new SwitchSummonPhase( + user.scene, + this.switchType, + switchOutTarget.getFieldIndex(), + slotIndex, + false, + false + ), + MoveEndPhase + ); + } else { + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); + user.scene.prependToPhase( + new SwitchSummonPhase( + user.scene, + this.switchType, + switchOutTarget.getFieldIndex(), + (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), + false, + false + ), + MoveEndPhase + ); + } } - } else { + } else { // Switch out logic for wild pokemon /** * Check if Wimp Out/Emergency Exit activates due to being hit by U-turn or Volt Switch * If it did, the user of U-turn or Volt Switch will not be switched out. */ - if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) && - (move.id === Moves.U_TURN || move.id === Moves.VOLT_SWITCH) || move.id === Moves.FLIP_TURN) { + if (target.getAbility().hasAttr(PostDamageForceSwitchAbAttr) + && [ Moves.U_TURN, Moves.VOLT_SWITCH, Moves.FLIP_TURN ].includes(move.id) + ) { if (this.hpDroppedBelowHalf(target)) { return false; } } - // Switch out logic for everything else (eg: WILD battles) if (user.scene.currentBattle.waveIndex % 10 === 0) { return false; } @@ -5912,7 +6139,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { user.scene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - user.scene.pushPhase(new BattleEndPhase(user.scene)); + user.scene.pushPhase(new BattleEndPhase(user.scene, false)); user.scene.pushPhase(new NewBattlePhase(user.scene)); } } @@ -5941,6 +6168,12 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return false; } + // Dondozo with an allied Tatsugiri in its mouth cannot be forced out + const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED); + if (commandedTag?.getSourcePokemon(switchOutTarget.scene)?.isActive(true)) { + return false; + } + if (!player && user.scene.currentBattle.isBattleMysteryEncounter() && !user.scene.currentBattle.mysteryEncounter?.fleeAllowed) { // Don't allow wild opponents to be force switched during MEs with flee disabled return false; @@ -6295,10 +6528,17 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } export class RandomMoveAttr extends OverrideMoveEffectAttr { + /** + * This function exists solely to allow tests to override the randomly selected move by mocking this function. + */ + public getMoveOverride(): Moves | null { + return null; + } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { 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 moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; const moveTargets = getMoveTargets(user, moveId); if (!moveTargets.targets.length) { @@ -6505,6 +6745,126 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { } } +/** + * Attribute used for moves that causes the target to repeat their last used move. + * + * Used for [Instruct](https://bulbapedia.bulbagarden.net/wiki/Instruct_(move)). +*/ +export class RepeatMoveAttr extends MoveEffectAttr { + constructor() { + super(false, { trigger: MoveEffectTrigger.POST_APPLY }); // needed to ensure correct protect interaction + } + + /** + * Forces the target to re-use their last used move again + * + * @param user {@linkcode Pokemon} that used the attack + * @param target {@linkcode Pokemon} targeted by the attack + * @param move N/A + * @param args N/A + * @returns `true` if the move succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + // get the last move used (excluding status based failures) as well as the corresponding moveset slot + const lastMove = target.getLastXMoves(-1).find(m => m.move !== Moves.NONE)!; + const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move)!; + const moveTargets = lastMove.targets ?? []; + + user.scene.queueMessage(i18next.t("moveTriggers:instructingMove", { + userPokemonName: getPokemonNameWithAffix(user), + targetPokemonName: getPokemonNameWithAffix(target) + })); + target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); + target.turnData.extraTurns++; + target.scene.appendToPhase(new MovePhase(target.scene, target, moveTargets, movesetMove), MoveEndPhase); + return true; + } + + getCondition(): MoveConditionFunc { + return (user, target, move) => { + // TODO: Confirm behavior of instructing move known by target but called by another move + const lastMove = target.getLastXMoves(-1).find(m => m.move !== Moves.NONE); + const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove?.move); + const moveTargets = lastMove?.targets ?? []; + // TODO: Add a way of adding moves to list procedurally rather than a pre-defined blacklist + const unrepeatablemoves = [ + // Locking/Continually Executed moves + Moves.OUTRAGE, + Moves.RAGING_FURY, + Moves.ROLLOUT, + Moves.PETAL_DANCE, + Moves.THRASH, + Moves.ICE_BALL, + // Multi-turn Moves + Moves.BIDE, + Moves.SHELL_TRAP, + Moves.BEAK_BLAST, + Moves.FOCUS_PUNCH, + // "First Turn Only" moves + Moves.FAKE_OUT, + Moves.FIRST_IMPRESSION, + Moves.MAT_BLOCK, + // Moves with a recharge turn + Moves.HYPER_BEAM, + Moves.ETERNABEAM, + Moves.FRENZY_PLANT, + Moves.BLAST_BURN, + Moves.HYDRO_CANNON, + Moves.GIGA_IMPACT, + Moves.PRISMATIC_LASER, + Moves.ROAR_OF_TIME, + Moves.ROCK_WRECKER, + Moves.METEOR_ASSAULT, + // Charging & 2-turn moves + Moves.DIG, + Moves.FLY, + Moves.BOUNCE, + Moves.SHADOW_FORCE, + Moves.PHANTOM_FORCE, + Moves.DIVE, + Moves.ELECTRO_SHOT, + Moves.ICE_BURN, + Moves.GEOMANCY, + Moves.FREEZE_SHOCK, + Moves.SKY_DROP, + Moves.SKY_ATTACK, + Moves.SKULL_BASH, + Moves.SOLAR_BEAM, + Moves.SOLAR_BLADE, + Moves.METEOR_BEAM, + // Other moves + Moves.INSTRUCT, + Moves.KINGS_SHIELD, + Moves.SKETCH, + Moves.TRANSFORM, + Moves.MIMIC, + Moves.STRUGGLE, + // TODO: Add Max/G-Move blockage if or when they are implemented + ]; + + if (!movesetMove // called move not in target's moveset (dancer, forgetting the move, etc.) + || movesetMove.ppUsed === movesetMove.getMovePp() // move out of pp + || allMoves[lastMove?.move ?? Moves.NONE].isChargingMove() // called move is a charging/recharging move + || !moveTargets.length // called move has no targets + || unrepeatablemoves.includes(lastMove?.move ?? Moves.NONE)) { // called move is explicitly in the banlist + return false; + } + return true; + }; + } + + getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + // TODO: Make the AI acutally use instruct + /* Ideally, the AI would score instruct based on the scorings of the on-field pokemons' + * last used moves at the time of using Instruct (by the time the instructor gets to act) + * with respect to the user's side. + * In 99.9% of cases, this would be the pokemon's ally (unless the target had last + * used a move like Decorate on the user or its ally) + */ + return 2; + } +} + /** * Attribute used for moves that reduce PP of the target's last used move. * Used for Spite. @@ -6681,7 +7041,8 @@ export class SketchAttr extends MoveEffectAttr { return false; } - const targetMove = target.getMoveHistory().filter(m => !m.virtual).at(-1); + const targetMove = target.getLastXMoves(-1) + .find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual); if (!targetMove) { return false; } @@ -6909,6 +7270,9 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr { } } +/** + * Used by Transform + */ export class TransformAttr extends MoveEffectAttr { async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { if (!super.apply(user, target, move, args)) { @@ -6917,10 +7281,8 @@ export class TransformAttr extends MoveEffectAttr { const promises: Promise[] = []; 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(); // Power Trick's effect will not preserved after using Transform user.removeTag(BattlerTagType.POWER_TRICK); @@ -7316,6 +7678,8 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField()); }; +const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(Type.GHOST); + export type MoveAttrFilter = (attr: MoveAttr) => boolean; function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise { @@ -7384,6 +7748,27 @@ export class FirstMoveCondition extends MoveCondition { } } +/** + * Condition used by the move {@link https://bulbapedia.bulbagarden.net/wiki/Upper_Hand_(move) | Upper Hand}. + * Moves with this condition are only successful when the target has selected + * a high-priority attack (after factoring in priority-boosting effects) and + * hasn't moved yet this turn. + */ +export class UpperHandCondition extends MoveCondition { + constructor() { + super((user, target, move) => { + const targetCommand = user.scene.currentBattle.turnCommands[target.getBattlerIndex()]; + + return !!targetCommand + && targetCommand.command === Command.FIGHT + && !target.turnData.acted + && !!targetCommand.move?.move + && allMoves[targetCommand.move.move].category !== MoveCategory.STATUS + && allMoves[targetCommand.move.move].getPriority(target) > 0; + }); + } +} + export class hitsSameTypeAttr extends VariableMoveTypeMultiplierAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const multiplier = args[0] as Utils.NumberHolder; @@ -7618,11 +8003,10 @@ export function initMoves() { .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(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) .ignoresSubstitute() .hidesTarget() - .windMove() - .partial(), // Should force random switches + .windMove(), new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) .chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) @@ -7698,10 +8082,9 @@ export function initMoves() { .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1) - .attr(ForceSwitchOutAttr) + .attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) .soundBased() - .hidesTarget() - .partial(), // Should force random switching + .hidesTarget(), new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.SLEEP) .soundBased(), @@ -7970,7 +8353,8 @@ export function initMoves() { .ignoresVirtual(), new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) - .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) + // transforming from or into fusion pokemon causes various problems (such as crashes) + .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies) .ignoresProtect(), new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) @@ -8042,6 +8426,7 @@ export function initMoves() { 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) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.MIND_READER, Type.NORMAL, -1, 5, -1, 0, 2) .attr(IgnoreAccuracyAttr), @@ -8178,6 +8563,7 @@ export function initMoves() { new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2) .attr(StatStageChangeAttr, [ Stat.DEF ], 1, true), new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2) .attr(AddBattlerTagAttr, BattlerTagType.INFATUATED) @@ -8297,7 +8683,8 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .ballBombMove(), new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) - .partial() // Complete buggy mess + .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field + .ignoresProtect() .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(StatStageChangeAttr, [ Stat.DEF ], -1), @@ -8374,7 +8761,8 @@ export function initMoves() { new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3) .attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND) .ignoresSubstitute() - .target(MoveTarget.NEAR_ALLY), + .target(MoveTarget.NEAR_ALLY) + .condition(failIfSingleBattle), new StatusMove(Moves.TRICK, Type.PSYCHIC, 100, 10, -1, 0, 3) .unimplemented(), new StatusMove(Moves.ROLE_PLAY, Type.PSYCHIC, -1, 10, -1, 0, 3) @@ -8420,10 +8808,10 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false) .target(MoveTarget.ENEMY_SIDE), new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3) - .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN) + .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(), + .attr(AddBattlerTagAttr, BattlerTagType.GRUDGE, true, undefined, 1), 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) @@ -8555,6 +8943,7 @@ export function initMoves() { new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true), new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1), new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.ATK ], 1) @@ -8604,7 +8993,8 @@ export function initMoves() { .attr(ConfuseAttr) .pulseMove(), new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) - .partial() // Complete buggy mess + .partial() // cannot be used on multiple Pokemon on the same side in a double battle, hits immediately when called by Metronome/etc, should not apply abilities or held items if user is off the field + .ignoresProtect() .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(StatStageChangeAttr, [ Stat.SPATK ], -2, true), @@ -8629,7 +9019,7 @@ export function initMoves() { .attr(GyroBallPowerAttr) .ballBombMove(), new SelfStatusMove(Moves.HEALING_WISH, Type.PSYCHIC, -1, 10, -1, 0, 4) - .attr(SacrificialFullRestoreAttr) + .attr(SacrificialFullRestoreAttr, false, "moveTriggers:sacrificialFullRestore") .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), @@ -8726,7 +9116,7 @@ export function initMoves() { 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.FLOATING, true, true) + .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true, 5) .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, 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) @@ -8906,10 +9296,9 @@ export function initMoves() { 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) + .attr(SacrificialFullRestoreAttr, true, "moveTriggers:lunarDanceRestore") .danceMove() - .triageMove() - .unimplemented(), + .triageMove(), 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) @@ -9059,12 +9448,13 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) - .attr(ForceSwitchOutAttr) - .partial(), // Should force random switches + .attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) + .hidesTarget(), 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) + .condition(failIfSingleBattle) .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.isTransferable).reduce((v, m) => v + m.stackCount, 0))), @@ -9127,9 +9517,8 @@ export function initMoves() { 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) - .hidesTarget() - .partial(), // Should force random switches + .attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH) + .hidesTarget(), new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true), new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) @@ -9276,8 +9665,7 @@ export function initMoves() { .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) - .edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell. + .attr(FreezeDryAttr), new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -9352,6 +9740,7 @@ export function initMoves() { new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1) .ignoresSubstitute() + .condition(failIfSingleBattle) .target(MoveTarget.NEAR_ALLY), new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.SPATK ], -2), @@ -9359,9 +9748,9 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], -1, false, { condition: (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) + .attr(AddBattlerTagAttr, BattlerTagType.POWDER, false, true) .ignoresSubstitute() - .powderMove() - .unimplemented(), + .powderMove(), new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) @@ -9580,7 +9969,8 @@ export function initMoves() { 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), + .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false) + .condition(failIfSingleBattle), new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, -1, 0, 7) .attr(StatusEffectAttr, StatusEffect.POISON) .attr(StatStageChangeAttr, [ Stat.SPD ], -1), @@ -9626,7 +10016,7 @@ export function initMoves() { .condition( (user: Pokemon, target: Pokemon, move: Move) => isNonVolatileStatusEffect(target.status?.effect!)) // TODO: is this bang correct? .attr(HealAttr, 0.5) - .attr(HealStatusEffectAttr, false, ...getNonVolatileStatusEffects()) + .attr(HealStatusEffectAttr, false, getNonVolatileStatusEffects()) .triageMove(), new AttackMove(Moves.REVELATION_DANCE, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7) .danceMove() @@ -9638,7 +10028,8 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK ], -1), new StatusMove(Moves.INSTRUCT, Type.PSYCHIC, -1, 15, -1, 0, 7) .ignoresSubstitute() - .unimplemented(), + .attr(RepeatMoveAttr) + .edgeCase(), // incorrect interactions with Gigaton Hammer, Blood Moon & Torment new AttackMove(Moves.BEAK_BLAST, Type.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, -3, 7) .attr(BeakBlastHeaderAttr) .ballBombMove() @@ -9710,11 +10101,9 @@ export function initMoves() { .ignoresSubstitute() .partial(), // Does not steal stats new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) - .ignoresAbilities() - .edgeCase(), // Should not ignore abilities when called virtually (metronome) + .ignoresAbilities(), new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) - .ignoresAbilities() - .edgeCase(), // Should not ignore abilities when called virtually (metronome) + .ignoresAbilities(), new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1), new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) @@ -9737,8 +10126,7 @@ export function initMoves() { .punchingMove(), new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) - .ignoresAbilities() - .edgeCase(), // Should not ignore abilities when called virtually (metronome) + .ignoresAbilities(), /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) @@ -9850,6 +10238,7 @@ export function initMoves() { .attr(EatBerryAttr) .target(MoveTarget.ALL), new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8) + .condition(failIfGhostTypeCondition) .attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1), new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8) .attr(FirstAttackDoublePowerAttr), @@ -10037,7 +10426,8 @@ export function initMoves() { .unimplemented(), new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1) - .target(MoveTarget.NEAR_ALLY), + .target(MoveTarget.NEAR_ALLY) + .condition(failIfSingleBattle), new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8) .attr(ForceSwitchOutAttr, true), new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8) @@ -10052,7 +10442,7 @@ export function initMoves() { .attr(StatusEffectAttr, StatusEffect.BURN), new StatusMove(Moves.JUNGLE_HEALING, Type.GRASS, -1, 10, -1, 0, 8) .attr(HealAttr, 0.25, true, false) - .attr(HealStatusEffectAttr, false, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP) + .attr(HealStatusEffectAttr, false, getNonVolatileStatusEffects()) .target(MoveTarget.USER_AND_ALLIES), new AttackMove(Moves.WICKED_BLOW, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) .attr(CritOnlyAttr) @@ -10156,12 +10546,12 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new StatusMove(Moves.LUNAR_BLESSING, Type.PSYCHIC, -1, 5, -1, 0, 8) .attr(HealAttr, 0.25, true, false) - .attr(HealStatusEffectAttr, false, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP) + .attr(HealStatusEffectAttr, false, getNonVolatileStatusEffects()) .target(MoveTarget.USER_AND_ALLIES) .triageMove(), new SelfStatusMove(Moves.TAKE_HEART, Type.PSYCHIC, -1, 10, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF ], 1, true) - .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP), + .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) @@ -10284,8 +10674,8 @@ export function initMoves() { new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9) - .makesContact(false) - .partial(), // No effect implemented (requires Commander) + .attr(OrderUpStatBoostAttr) + .makesContact(false), 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) @@ -10391,6 +10781,7 @@ export function initMoves() { 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) + .partial() // Counter resets every wave instead of on arena reset .attr(HitCountPowerAttr) .punchingMove(), new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) @@ -10499,8 +10890,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2), 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: is this bang correct? - .partial(), // Should also apply when target move priority increased by ability ex. gale wings + .condition(new UpperHandCondition()), new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) .attr(StatusEffectAttr, StatusEffect.TOXIC) ); diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index fa8e1aed1c7..6b0f239d28d 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -18,7 +18,7 @@ import { randInt } from "#app/utils"; import { BattlerIndex } from "#app/battle"; import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#app/data/trainer-config"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; @@ -216,6 +216,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = species: getPokemonSpecies(Species.GREEDENT), isBoss: true, bossSegments: 3, + shiny: false, // Shiny lock because of consistency issues between the different options moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ], modifierConfigs: bossModifierConfigs, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], @@ -353,9 +354,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Let it have the food - // Greedent joins the team, level equal to 2 below highest party member + // Greedent joins the team, level equal to 2 below highest party member (shiny locked) const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2; - const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false); + const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true); greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ]; greedent.passive = true; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 5524511c67b..786ca3e8fc0 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -98,7 +98,9 @@ export const BerriesAboundEncounter: MysteryEncounter = tint: 0.25, x: -5, repeat: true, - isPokemon: true + isPokemon: true, + isShiny: bossPokemon.shiny, + variant: bossPokemon.variant } ]; diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index f2605795955..ecd6972902b 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -36,7 +36,7 @@ import { HeldItemRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { AttackTypeBoosterModifierType, ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import { AttackTypeBoosterModifier, @@ -477,12 +477,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = .withOptionPhase(async (scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; + const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; - // Remove the modifier if its stacks go to 0 - modifier.stackCount -= 1; - if (modifier.stackCount === 0) { - scene.removeModifier(modifier); - } + chosenPokemon.loseHeldItem(modifier, false); scene.updateModifiers(true, true); const bugNet = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 5502cc7b53a..6bd6856604b 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -12,7 +12,7 @@ import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Abilities } from "#enums/abilities"; import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { randSeedInt, randSeedShuffle } from "#app/utils"; @@ -276,6 +276,8 @@ export const ClowningAroundEncounter: MysteryEncounter = generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries"); // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) + // For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier + // And Golden Eggs as Rogue tier let numUltra = 0; let numRogue = 0; items.filter(m => m.isTransferable && !(m instanceof BerryModifier)) @@ -285,7 +287,7 @@ export const ClowningAroundEncounter: MysteryEncounter = if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { numRogue += m.stackCount; scene.removeModifier(m); - } else if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) { + } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) { numUltra += m.stackCount; scene.removeModifier(m); } @@ -456,7 +458,6 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem [ modifierTypes.LEFTOVERS, 4 ], [ modifierTypes.SHELL_BELL, 4 ], [ modifierTypes.SOUL_DEW, 10 ], - [ modifierTypes.SOOTHE_BELL, 3 ], [ modifierTypes.SCOPE_LENS, 1 ], [ modifierTypes.BATON, 1 ], [ modifierTypes.FOCUS_BAND, 5 ], diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index bae5a8790e9..841aadd7c36 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -92,9 +92,13 @@ export const DancingLessonsEncounter: MysteryEncounter = .withCatchAllowed(true) .withFleeAllowed(false) .withOnVisualsStart((scene: BattleScene) => { - const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon()!); - danceAnim.play(scene); - + const oricorio = scene.getEnemyPokemon()!; + const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!); + danceAnim.play(scene, false, () => { + if (oricorio.shiny) { + oricorio.sparkle(); + } + }); return true; }) .withIntroDialogue([ @@ -136,7 +140,7 @@ export const DancingLessonsEncounter: MysteryEncounter = } const oricorioData = new PokemonData(enemyPokemon); - const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData); + const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); // Adds a real Pokemon sprite to the field (required for the animation) scene.getEnemyParty().forEach(enemyPokemon => { diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 8a814b58248..05d6501f256 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -1,4 +1,4 @@ -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; @@ -8,7 +8,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; -import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; @@ -174,7 +174,7 @@ export const DarkDealEncounter: MysteryEncounter = const roll = randSeedInt(100); const starterTier: number | [number, number] = roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [ 9, 10 ]; - const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes)); + const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterCost(starterTier, excludedBosses, bossTypes)); const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index d5a938b9cef..a3a97a01238 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -8,7 +8,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u import { getPokemonSpecies } from "#app/data/pokemon-species"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier"; +import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import i18next from "#app/plugins/i18n"; @@ -197,7 +197,8 @@ export const DelibirdyEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; - const modifier: BerryModifier | HealingBoosterModifier = encounter.misc.chosenModifier; + const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier; + const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed if (modifier instanceof BerryModifier) { @@ -228,11 +229,7 @@ export const DelibirdyEncounter: MysteryEncounter = } } - // Remove the modifier if its stacks go to 0 - modifier.stackCount -= 1; - if (modifier.stackCount === 0) { - scene.removeModifier(modifier); - } + chosenPokemon.loseHeldItem(modifier, false); leaveEncounterWithoutBattle(scene, true); }) @@ -292,6 +289,7 @@ export const DelibirdyEncounter: MysteryEncounter = .withOptionPhase(async (scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; + const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check if the player has max stacks of Healing Charm already const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; @@ -306,11 +304,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); } - // Remove the modifier if its stacks go to 0 - modifier.stackCount -= 1; - if (modifier.stackCount === 0) { - scene.removeModifier(modifier); - } + chosenPokemon.loseHeldItem(modifier, false); leaveEncounterWithoutBattle(scene, true); }) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 5794277ffe1..bbc979e844e 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -8,14 +8,14 @@ import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } fr import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { BattlerIndex } from "#app/battle"; import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; -import { WeatherType } from "#app/data/weather"; +import { WeatherType } from "#enums/weather-type"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 3f9030dc3b2..3533e10df29 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -114,7 +114,9 @@ export const FightOrFlightEncounter: MysteryEncounter = tint: 0.25, x: -5, repeat: true, - isPokemon: true + isPokemon: true, + isShiny: bossPokemon.shiny, + variant: bossPokemon.variant }, ]; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 7bf48aa5926..84c3e56a836 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -194,10 +194,10 @@ async function summonPlayerPokemon(scene: BattleScene) { playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon); }); - // Also loads Wobbuffet data + // Also loads Wobbuffet data (cannot be shiny) const enemySpecies = getPokemonSpecies(Species.WOBBUFFET); scene.currentBattle.enemyParty = []; - const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false); + const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true); wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ]; wobbuffet.setNature(Nature.MILD); wobbuffet.setAlpha(0); @@ -305,7 +305,7 @@ async function showWobbuffetHealthBar(scene: BattleScene) { scene.field.add(wobbuffet); const playerPokemon = scene.getPlayerPokemon() as Pokemon; - if (playerPokemon?.visible) { + if (playerPokemon?.isOnField()) { scene.field.moveBelow(wobbuffet, playerPokemon); } // Show health bar and trigger cry diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 90f6ee942be..fa445d75d4f 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -12,8 +12,7 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon import { getTypeRgb } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import * as Utils from "#app/utils"; -import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils"; +import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -21,11 +20,13 @@ import PokemonData from "#app/system/pokemon-data"; import i18next from "i18next"; import { Gender, getGenderSymbol } from "#app/data/gender"; import { getNatureName } from "#app/data/nature"; -import { getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/data/pokeball"; +import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { trainerNamePools } from "#app/data/trainer-names"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import type { PokeballType } from "#enums/pokeball"; +import { doShinySparkleAnim } from "#app/field/anims"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -229,7 +230,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { - const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE); + const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE); if (scene.eventManager.isEventActive()) { shinyThreshold.value *= scene.eventManager.getShinyMultiplier(); } @@ -246,7 +247,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1; if (tradePokemon.species.abilityHidden) { if (tradePokemon.abilityIndex < hiddenIndex) { - const hiddenAbilityChance = new IntegerHolder(64); + const hiddenAbilityChance = new NumberHolder(64); scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -344,6 +345,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = // Pokemon and item selected encounter.setDialogueToken("chosenItem", modifier.type.name); encounter.misc.chosenModifier = modifier; + encounter.misc.chosenPokemon = pokemon; return true; }, }; @@ -367,10 +369,12 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; - const modifier = encounter.misc.chosenModifier; + const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier; + const party = scene.getPlayerParty(); + const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up - const type = modifier.type.withTierFromPool(); + const type = modifier.type.withTierFromPool(ModifierPoolType.PLAYER, party); let tier = type.tier ?? ModifierTier.GREAT; // Eggs and White Herb are not in the pool if (type.id === "WHITE_HERB") { @@ -385,21 +389,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = tier++; } - regenerateModifierPoolThresholds(scene.getPlayerParty(), ModifierPoolType.PLAYER, 0); + regenerateModifierPoolThresholds(party, ModifierPoolType.PLAYER, 0); let item: ModifierTypeOption | null = null; // TMs excluded from possible rewards while (!item || item.type.id.includes("TM_")) { - item = getPlayerModifierTypeOptions(1, scene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0]; + item = getPlayerModifierTypeOptions(1, party, [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0]; } encounter.setDialogueToken("itemName", item.type.name); setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); - // Remove the chosen modifier if its stacks go to 0 - modifier.stackCount -= 1; - if (modifier.stackCount === 0) { - scene.removeModifier(modifier); - } + chosenPokemon.loseHeldItem(modifier, false); await scene.updateModifiers(true, true); // Generate a trainer name @@ -582,7 +582,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon receivedPokemonTintSprite.setTintFill(getPokeballTintColor(receivedPokemon.pokeball)); [ tradedPokemonSprite, tradedPokemonTintSprite ].map(sprite => { - sprite.play(tradedPokemon.getSpriteKey(true)); + const spriteKey = tradedPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); @@ -597,7 +603,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon }); [ receivedPokemonSprite, receivedPokemonTintSprite ].map(sprite => { - sprite.play(receivedPokemon.getSpriteKey(true)); + const spriteKey = receivedPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); @@ -797,6 +809,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2; receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100; + // Received pokemon sparkles + let pokemonShinySparkle: Phaser.GameObjects.Sprite; + if (receivedPokemon.shiny) { + pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny"); + pokemonShinySparkle.setVisible(false); + tradeContainer.add(pokemonShinySparkle); + } + const BASE_ANIM_DURATION = 1000; // Pokeball falls to the screen @@ -835,6 +855,11 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke scale: 1, alpha: 0, onComplete: () => { + if (receivedPokemon.shiny) { + scene.time.delayedCall(500, () => { + doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant); + }); + } receivedPokeballSprite.destroy(); scene.time.delayedCall(2000, () => resolve()); } diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index ab6517e97af..877deee66b7 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -34,6 +34,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withScenePartySizeRequirement(2, 6, true) .withAutoHideIntroVisuals(false) .withCatchAllowed(true) .withIntroSpriteConfigs([ diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 0ee3c57b0a2..e16cf2d6973 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -6,12 +6,12 @@ import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data import { TrainerSlot } from "#app/data/trainer-config"; import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier"; import { EnemyPokemon } from "#app/field/pokemon"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { IntegerHolder, randSeedInt } from "#app/utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -19,6 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { ScanIvsPhase } from "#app/phases/scan-ivs-phase"; import { SummonPhase } from "#app/phases/summon-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/safariZone"; @@ -261,7 +262,7 @@ async function summonSafariPokemon(scene: BattleScene) { let enemySpecies; let pokemon; scene.executeWithSeedOffset(() => { - enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false)); + enemySpecies = getSafariSpeciesSpawn(); const level = scene.currentBattle.getLevelForWave(); enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode)); pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false); @@ -526,3 +527,10 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) { initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); } } + +/** + * @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc. + */ +export function getSafariSpeciesSpawn(): PokemonSpecies { + return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false)); +} diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 798e85868f6..3f1ace47b0f 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = newNature = randSeedInt(25) as Nature; } - chosenPokemon.customPokemonData.nature = newNature; + chosenPokemon.setCustomNature(newNature); encounter.setDialogueToken("newNature", getNatureName(newNature)); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 8ea19e1225b..8dd03e12caa 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -3,7 +3,7 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; @@ -60,6 +60,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, + shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ], modifierConfigs: [ diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index faeba95f358..042e9278673 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -12,7 +12,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { Biome } from "#enums/biome"; import { getBiomeKey } from "#app/field/arena"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { getPartyLuckValue, modifierTypes } from "#app/modifier/modifier-type"; import { TrainerSlot } from "#app/data/trainer-config"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 7c904bd047a..5ac9852f27a 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -21,9 +21,8 @@ import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { achvs } from "#app/system/achv"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { getPokeballTintColor } from "#app/data/pokeball"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; @@ -520,12 +519,6 @@ function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter: ]; } -function checkAchievement(scene: BattleScene) { - if (scene.arena.biomeType === Biome.SPACE) { - scene.validateAchv(achvs.BREEDERS_IN_SPACE); - } -} - function restorePartyAndHeldItems(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; // Restore original party @@ -617,8 +610,6 @@ function onGameOver(scene: BattleScene) { function doPostEncounterCleanup(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; if (!encounter.misc.encounterFailed) { - // Give achievement if in Space biome - checkAchievement(scene); // Give 20 friendship to the chosen pokemon encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); restorePartyAndHeldItems(scene); diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 95f359547e4..feb6e68d1d1 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -4,11 +4,11 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Species } from "#enums/species"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -17,6 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Abilities } from "#enums/abilities"; +import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/thePokemonSalesman"; @@ -60,24 +61,22 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = .withOnInit((scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; - let species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false)); + let species = getSalesmanSpeciesOffer(); let tries = 0; // Reroll any species that don't have HAs while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) { - species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false)); + species = getSalesmanSpeciesOffer(); tries++; } let pokemon: PlayerPokemon; if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) { - // If no HA mon found or you roll 1%, give shiny Magikarp + // If no HA mon found or you roll 1%, give shiny Magikarp with random variant species = getPokemonSpecies(Species.MAGIKARP); - const hiddenIndex = species.ability2 ? 2 : 1; - pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0); + pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true); } else { - const hiddenIndex = species.ability2 ? 2 : 1; - pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex); + pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex); } pokemon.generateAndPopulateMoveset(); @@ -87,7 +86,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = fileRoot: fileRoot, hasShadow: true, repeat: true, - isPokemon: true + isPokemon: true, + isShiny: pokemon.shiny, + variant: pokemon.variant }); const starterTier = speciesStarterCosts[species.speciesId]; @@ -164,3 +165,10 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = } ) .build(); + +/** + * @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc. + */ +export function getSalesmanSpeciesOffer(): PokemonSpecies { + return getPokemonSpecies(getRandomSpeciesByStarterCost([ 0, 5 ], NON_LEGEND_PARADOX_POKEMON, undefined, false, false, false)); +} diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index b1ef6cedb21..c5cfd3f954e 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -5,7 +5,7 @@ import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -79,6 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, + shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 7d9b531c9ab..f4446241873 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -10,7 +10,7 @@ import { Abilities } from "#enums/abilities"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { Nature } from "#enums/nature"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; @@ -23,6 +23,7 @@ import { ReturnPhase } from "#app/phases/return-phase"; import i18next from "i18next"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { BattlerTagType } from "#enums/battler-tag-type"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -187,6 +188,7 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { } else { scene.arena.resetArenaEffects(); const playerField = scene.getPlayerField(); + playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p))); for (const pokemon of scene.getPlayerParty()) { diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 8814eb0d8cf..725c4ba79eb 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -1,6 +1,6 @@ import { Ability, allAbilities } from "#app/data/ability"; import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { getNatureName, Nature } from "#app/data/nature"; +import { getNatureName } from "#app/data/nature"; import { speciesStarterCosts } from "#app/data/balance/starters"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; @@ -21,6 +21,7 @@ import i18next from "i18next"; import { getStatKey } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import type { Nature } from "#enums/nature"; /** The i18n namespace for the encounter */ const namespace = "mysteryEncounters/trainingSession"; @@ -225,8 +226,8 @@ export const TrainingSessionEncounter: MysteryEncounter = const onBeforeRewardsPhase = () => { queueEncounterMessage(scene, `${namespace}:option.2.finished`); // Add the pokemon back to party with Nature change - playerPokemon.setNature(encounter.misc.chosenNature); - scene.gameData.setPokemonCaught(playerPokemon, false); + playerPokemon.setCustomNature(encounter.misc.chosenNature); + scene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature); // Add pokemon and modifiers back scene.getPlayerParty().push(playerPokemon); diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index fba3a6ca95e..dfd89cdfb63 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -61,11 +61,12 @@ export const TrashToTreasureEncounter: MysteryEncounter = .withOnInit((scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; - // Calculate boss mon + // Calculate boss mon (shiny locked) const bossSpecies = getPokemonSpecies(Species.GARBODOR); const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, + shiny: false, // Shiny lock because of custom intro sprite formIndex: 1, // Gmax bossSegmentModifier: 1, // +1 Segment from normal moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ] diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index a2c32c6af40..d3679825ac8 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -100,7 +100,9 @@ export const UncommonBreedEncounter: MysteryEncounter = hasShadow: true, x: -5, repeat: true, - isPokemon: true + isPokemon: true, + isShiny: pokemon.shiny, + variant: pokemon.variant }, ]; @@ -113,13 +115,15 @@ export const UncommonBreedEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const pokemonSprite = encounter.introVisuals!.getSprites(); - scene.tweens.add({ // Bounce at the end + // Bounce at the end, then shiny sparkle if the Pokemon is shiny + scene.tweens.add({ targets: pokemonSprite, duration: 300, ease: "Cubic.easeOut", yoyo: true, y: "-=20", loop: 1, + onComplete: () => encounter.introVisuals?.playShinySparkles() }); scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index c05e707ed4d..3d2e8493d44 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -1,4 +1,4 @@ -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; @@ -312,6 +312,7 @@ export const WeirdDreamEncounter: MysteryEncounter = pokemon.levelExp = 0; pokemon.calculateStats(); + pokemon.getBattleInfo().setLevel(pokemon.level); await pokemon.updateInfo(); } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index c674ebdc46e..4ff8fd95f85 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -2,7 +2,7 @@ import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounte import { Moves } from "#app/enums/moves"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import BattleScene from "#app/battle-scene"; -import { Type } from "../type"; +import { Type } from "#enums/type"; import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 1358c465d17..811b622de76 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,11 +1,11 @@ import BattleScene from "#app/battle-scene"; import { allAbilities } from "#app/data/ability"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; -import { StatusEffect } from "#app/data/status-effect"; -import { Type } from "#app/data/type"; -import { WeatherType } from "#app/data/weather"; +import { StatusEffect } from "#enums/status-effect"; +import { Type } from "#enums/type"; +import { WeatherType } from "#enums/weather-type"; import { PlayerPokemon } from "#app/field/pokemon"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index badbfcf7a56..e341da4e435 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -5,7 +5,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import * as Utils from "#app/utils"; -import { StatusEffect } from "../status-effect"; +import { StatusEffect } from "#enums/status-effect"; import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option"; import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements"; diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index c4d5e47cb05..acaa7c6244f 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -7,22 +7,22 @@ import i18next from "i18next"; /** * Will inject all relevant dialogue tokens that exist in the {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text. * Also adds BBCodeText fragments for colored text, if applicable - * @param scene * @param keyOrString * @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly - * @param uiTheme */ -export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null { +export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle): string | null { if (isNullOrUndefined(keyOrString)) { return null; } + const uiTheme = scene.uiTheme ?? UiTheme.DEFAULT; + let textString: string | null = getTextWithDialogueTokens(scene, keyOrString); // Can only color the text if a Primary Style is defined // primaryStyle is applied to all text that does not have its own specified style if (primaryStyle && textString) { - textString = getTextWithColors(textString, primaryStyle, uiTheme); + textString = getTextWithColors(textString, primaryStyle, uiTheme, true); } return textString; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 429b6bfa292..d43bce0ace5 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -19,11 +19,11 @@ import i18next from "i18next"; import BattleScene from "#app/battle-scene"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import { Gender } from "#app/data/gender"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { Moves } from "#enums/moves"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import PokemonSpecies from "#app/data/pokemon-species"; import { Egg, IEggOptions } from "#app/data/egg"; @@ -37,6 +37,7 @@ import { GameOverPhase } from "#app/phases/game-over-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { Variant } from "#app/data/variant"; +import { StatusEffect } from "#enums/status-effect"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -183,7 +184,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: dataSource = config.dataSource; enemySpecies = config.species; isBoss = config.isBoss; - battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource); + battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource); } else { battle.enemyParty[e] = battle.trainer.genPartyMember(e); } @@ -201,7 +202,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemySpecies = scene.randomSpecies(battle.waveIndex, level, true); } - battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, dataSource); + battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource); } } @@ -730,7 +731,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); scene.pushPhase(new EggLapsePhase(scene)); } else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { - scene.pushPhase(new BattleEndPhase(scene)); + scene.pushPhase(new BattleEndPhase(scene, true)); if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { scene.pushPhase(new TrainerVictoryPhase(scene)); } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index dd42cc9ce55..072b5e5b160 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -3,15 +3,15 @@ import i18next from "i18next"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "#app/data/pokeball"; +import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; -import { getStatusEffectCatchRateMultiplier, StatusEffect } from "#app/data/status-effect"; +import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { achvs } from "#app/system/achv"; import { Mode } from "#app/ui/ui"; import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { Species } from "#enums/species"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -23,6 +23,8 @@ import { VictoryPhase } from "#app/phases/victory-phase"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Abilities } from "#enums/abilities"; +import type { PokeballType } from "#enums/pokeball"; +import { StatusEffect } from "#enums/status-effect"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -205,7 +207,7 @@ export function getHighestStatTotalPlayerPokemon(scene: BattleScene, isAllowed: * @param allowMythical * @returns */ -export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species { +export function getRandomSpeciesByStarterCost(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species { let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers; let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers; @@ -288,7 +290,10 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, if (damage <= 0) { console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead."); } - + // If a Pokemon would faint from the damage applied, its HP is instead set to 1. + if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) { + damage = pokemon.hp - 1; + } applyHpChangeToPokemon(scene, pokemon, -damage); } diff --git a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts index fcadb101817..424ba15f811 100644 --- a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts +++ b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts @@ -54,7 +54,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke pokemonEvoTintSprite.setTintFill(0xFFFFFF); [ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { - sprite.play(previousPokemon.getSpriteKey(true)); + const spriteKey = previousPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); @@ -69,7 +75,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke }); [ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { - sprite.play(transformPokemon.getSpriteKey(true)); + const spriteKey = transformPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey()); sprite.setPipelineData("shiny", transformPokemon.shiny); diff --git a/src/data/nature.ts b/src/data/nature.ts index edac06f1a4f..b90047ce6f0 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -3,9 +3,7 @@ import { TextStyle, getBBCodeFrag } from "../ui/text"; import { Nature } from "#enums/nature"; import { UiTheme } from "#enums/ui-theme"; import i18next from "i18next"; -import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#app/enums/stat"; - -export { Nature }; +import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#enums/stat"; export function getNatureName(nature: Nature, includeStatEffects: boolean = false, forStarterSelect: boolean = false, ignoreBBCode: boolean = false, uiTheme: UiTheme = UiTheme.DEFAULT): string { let ret = Utils.toReadableString(Nature[nature]); diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 57a78e2cd61..4c9fc719a4d 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,9 +1,9 @@ +import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; +import { NumberHolder } from "#app/utils"; import { PokeballType } from "#enums/pokeball"; import BattleScene from "../battle-scene"; import i18next from "i18next"; -export { PokeballType }; - export const MAX_PER_TYPE_POKEBALLS: integer = 99; export function getPokeballAtlasKey(type: PokeballType): string { @@ -82,11 +82,38 @@ export function getPokeballTintColor(type: PokeballType): number { } } -export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: integer, callback: Function) { +/** + * Gets the critical capture chance based on number of mons registered in Dex and modified {@link https://bulbapedia.bulbagarden.net/wiki/Catch_rate Catch rate} + * Formula from {@link https://www.dragonflycave.com/mechanics/gen-vi-vii-capturing Dragonfly Cave Gen 6 Capture Mechanics page} + * @param scene {@linkcode BattleScene} current BattleScene + * @param modifiedCatchRate the modified catch rate as calculated in {@linkcode AttemptCapturePhase} + * @returns the chance of getting a critical capture, out of 256 + */ +export function getCriticalCaptureChance(scene: BattleScene, modifiedCatchRate: number): number { + if (scene.gameMode.isFreshStartChallenge()) { + return 0; + } + const dexCount = scene.gameData.getSpeciesCount(d => !!d.caughtAttr); + const catchingCharmMultiplier = new NumberHolder(1); + scene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); + const dexMultiplier = scene.gameMode.isDaily || dexCount > 800 ? 2.5 + : dexCount > 600 ? 2 + : dexCount > 400 ? 1.5 + : dexCount > 200 ? 1 + : dexCount > 100 ? 0.5 + : 0; + return Math.floor(catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate) / 6); +} + +export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: number, callback: Function, isCritical: boolean = false) { let bouncePower = 1; let bounceYOffset = y1; let bounceY = y2; const yd = y2 - y1; + const x0 = pokeball.x; + const x1 = x0 + 3; + const x2 = x0 - 3; + let critShakes = 4; const doBounce = () => { scene.tweens.add({ @@ -117,5 +144,40 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb }); }; - doBounce(); + const doCritShake = () => { + scene.tweens.add({ + targets: pokeball, + x: x2, + duration: 125, + ease: "Linear", + onComplete: () => { + scene.tweens.add({ + targets: pokeball, + x: x1, + duration: 125, + ease: "Linear", + onComplete: () => { + critShakes--; + if (critShakes > 0) { + doCritShake(); + } else { + scene.tweens.add({ + targets: pokeball, + x: x0, + duration: 60, + ease: "Linear", + onComplete: () => scene.time.delayedCall(500, doBounce) + }); + } + } + }); + } + }); + }; + + if (isCritical) { + scene.time.delayedCall(500, doCritShake); + } else { + doBounce(); + } } diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 7cc20d50fb9..a1b2d7896d7 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,8 +1,8 @@ import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; import Pokemon from "../field/pokemon"; -import { StatusEffect } from "./status-effect"; +import { StatusEffect } from "#enums/status-effect"; import { MoveCategory, allMoves } from "./move"; -import { Type } from "./type"; +import { Type } from "#enums/type"; import { Constructor, nil } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -10,7 +10,7 @@ import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; -import { WeatherType } from "./weather"; +import { WeatherType } from "#enums/weather-type"; import { Challenges } from "#app/enums/challenges"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -351,6 +351,10 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { return false; } else { + // Meloetta will not transform if it has the ability Sheer Force when using Relic Song + if (pokemon.hasAbility(Abilities.SHEER_FORCE)) { + return false; + } return super.canChange(pokemon); } } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 0cdeef10d9e..09788e353cf 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -12,10 +12,10 @@ import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import { Stat } from "#enums/stat"; -import { Variant, VariantSet, variantColorCache, variantData } from "#app/data/variant"; +import { Variant, VariantSet, variantData } from "#app/data/variant"; import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -363,6 +363,12 @@ export abstract class PokemonSpeciesForm { } switch (this.speciesId) { + case Species.DODUO: + case Species.DODRIO: + case Species.MEGANIUM: + case Species.TORCHIC: + case Species.COMBUSKEN: + case Species.BLAZIKEN: case Species.HIPPOPOTAS: case Species.HIPPOWDON: case Species.UNFEZANT: @@ -499,35 +505,14 @@ export abstract class PokemonSpeciesForm { scene.anims.create({ key: this.getSpriteKey(female, formIndex, shiny, variant), frames: frameNames, - frameRate: 12, + frameRate: 10, repeat: -1 }); } else { - scene.anims.get(spriteKey).frameRate = 12; - } - let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); - const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey); - if (useExpSprite) { - spritePath = `exp/${spritePath}`; - } - let config = variantData; - spritePath.split("/").map(p => config ? config = config[p] : null); - const variantSet = config as VariantSet; - if (variantSet && (variant !== undefined && variantSet[variant] === 1)) { - const populateVariantColors = (key: string): Promise => { - return new Promise(resolve => { - if (variantColorCache.hasOwnProperty(key)) { - return resolve(); - } - scene.cachedFetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => { - variantColorCache[key] = c; - resolve(); - }); - }); - }; - populateVariantColors(spriteKey).then(() => resolve()); - return; + scene.anims.get(spriteKey).frameRate = 10; } + const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); + scene.loadPokemonVariantAssets(spriteKey, spritePath, variant); resolve(); }); if (startLoad) { @@ -888,17 +873,24 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali getCompatibleFusionSpeciesFilter(): PokemonSpeciesFilter { const hasEvolution = pokemonEvolutions.hasOwnProperty(this.speciesId); const hasPrevolution = pokemonPrevolutions.hasOwnProperty(this.speciesId); - const pseudoLegendary = this.subLegendary; + const subLegendary = this.subLegendary; const legendary = this.legendary; const mythical = this.mythical; return species => { - return (pseudoLegendary || legendary || mythical || - (pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution - && pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution)) - && species.subLegendary === pseudoLegendary + return ( + subLegendary + || legendary + || mythical + || ( + pokemonEvolutions.hasOwnProperty(species.speciesId) === hasEvolution + && pokemonPrevolutions.hasOwnProperty(species.speciesId) === hasPrevolution + ) + ) + && species.subLegendary === subLegendary && species.legendary === legendary && species.mythical === mythical - && (this.isTrainerForbidden() || !species.isTrainerForbidden()); + && (this.isTrainerForbidden() || !species.isTrainerForbidden()) + && species.speciesId !== Species.DITTO; }; } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 56e754ac407..6b4e1d546df 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -2,8 +2,6 @@ import { randIntRange } from "#app/utils"; import { StatusEffect } from "#enums/status-effect"; import i18next, { ParseKeys } from "i18next"; -export { StatusEffect }; - export class Status { public effect: StatusEffect; /** Toxic damage is `1/16 max HP * toxicTurnCount` */ diff --git a/src/data/terrain.ts b/src/data/terrain.ts index d8ee8d67925..6ba9acfd166 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -1,8 +1,6 @@ import Pokemon from "../field/pokemon"; import Move from "./move"; -import { Type } from "./type"; -import * as Utils from "../utils"; -import { ChangeMovePriorityAbAttr, applyAbAttrs } from "./ability"; +import { Type } from "#enums/type"; import { ProtectAttr } from "./move"; import { BattlerIndex } from "#app/battle"; import i18next from "i18next"; @@ -58,10 +56,8 @@ export class Terrain { switch (this.terrainType) { case TerrainType.PSYCHIC: if (!move.hasAttr(ProtectAttr)) { - const priority = new Utils.IntegerHolder(move.priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain - return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); + return move.getPriority(user) > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); } } diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 18bcec1f928..d99ca601bdf 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -2,11 +2,11 @@ import BattleScene, { startingWave } from "#app/battle-scene"; import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type"; import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import * as Utils from "#app/utils"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { doubleBattleDialogue } from "#app/data/dialogue"; import { PersistentModifier } from "#app/modifier/modifier"; import { TrainerVariant } from "#app/field/trainer"; @@ -1173,16 +1173,28 @@ export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: Tr if (!ignoreEvolution) { species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex); } - return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess); + return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess); }; } -function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, trainerSlot: TrainerSlot = TrainerSlot.TRAINER, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc { - const originalSpeciesFilter = speciesFilter; - speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species); - return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => { - const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex)), level, trainerSlot, undefined, undefined, postProcess); - return ret; +function getSpeciesFilterRandomPartyMemberFunc( + originalSpeciesFilter: PokemonSpeciesFilter, + trainerSlot: TrainerSlot = TrainerSlot.TRAINER, + allowLegendaries?: boolean, + postProcess?: (EnemyPokemon: EnemyPokemon) => void +): PartyMemberFunc { + + const speciesFilter = (species: PokemonSpecies): boolean => { + const notLegendary = !species.legendary && !species.subLegendary && !species.mythical; + return (allowLegendaries || notLegendary) && !species.isTrainerForbidden() && originalSpeciesFilter(species); + }; + + return (scene: BattleScene, level: number, strength: PartyMemberStrength) => { + const waveIndex = scene.currentBattle.waveIndex; + const species = getPokemonSpecies(scene.randomSpecies(waveIndex, level, false, speciesFilter) + .getTrainerSpeciesForLevel(level, true, strength, waveIndex)); + + return scene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess); }; } @@ -1841,21 +1853,25 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL) .setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE) .setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM, () => modifierTypes.ABILITY_CHARM) - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY ], TrainerSlot.TRAINER, true)) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY ], TrainerSlot.TRAINER, true, + (p => p.abilityIndex = 0))) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEY, Species.HOOTHOOT, Species.TAILLOW, Species.STARLY, Species.PIDOVE, Species.FLETCHLING, Species.PIKIPEK, Species.ROOKIDEE, Species.WATTREL ], TrainerSlot.TRAINER, true)), [TrainerType.RIVAL_2]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setModifierRewardFuncs(() => modifierTypes.EXP_SHARE) .setEventModifierRewardFuncs(() => modifierTypes.SHINY_CHARM) - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL ], TrainerSlot.TRAINER, true)) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL ], TrainerSlot.TRAINER, true, + (p => p.abilityIndex = 0))) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOTTO, Species.HOOTHOOT, Species.TAILLOW, Species.STARAVIA, Species.TRANQUILL, Species.FLETCHINDER, Species.TRUMBEAK, Species.CORVISQUIRE, Species.WATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)), [TrainerType.RIVAL_3]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setStaticParty().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival").setMixedBattleBgm("battle_rival").setPartyTemplates(trainerPartyTemplates.RIVAL_3) - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true)) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, + (p => p.abilityIndex = 0))) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540), [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4) - .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true)) + .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, + (p => p.abilityIndex = 0))) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540) @@ -1865,7 +1881,10 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, - p => p.setBoss(true, 2))) + p => { + p.setBoss(true, 2); + p.abilityIndex = 0; + })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540) @@ -1883,6 +1902,7 @@ export const trainerConfigs: TrainerConfigs = { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, p => { p.setBoss(true, 3); + p.abilityIndex = 0; p.generateAndPopulateMoveset(); })) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true, diff --git a/src/data/type.ts b/src/data/type.ts index 483ec068d3c..6170eadc91e 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -1,25 +1,4 @@ -export enum Type { - UNKNOWN = -1, - NORMAL = 0, - FIGHTING, - FLYING, - POISON, - GROUND, - ROCK, - BUG, - GHOST, - STEEL, - FIRE, - WATER, - GRASS, - ELECTRIC, - PSYCHIC, - ICE, - DRAGON, - DARK, - FAIRY, - STELLAR -} +import { Type } from "#enums/type"; export type TypeDamageMultiplier = 0 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8; diff --git a/src/data/weather.ts b/src/data/weather.ts index 20c03af77c8..0a76a015402 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -2,7 +2,7 @@ import { Biome } from "#enums/biome"; import { WeatherType } from "#enums/weather-type"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon from "../field/pokemon"; -import { Type } from "./type"; +import { Type } from "#enums/type"; import Move, { AttackMove } from "./move"; import * as Utils from "../utils"; import BattleScene from "../battle-scene"; @@ -10,7 +10,6 @@ import { SuppressWeatherEffectAbAttr } from "./ability"; import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; -export { WeatherType }; export class Weather { public weatherType: WeatherType; public turnsLeft: integer; diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 0eace9a1607..f28ac37ae27 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -88,5 +88,10 @@ export enum BattlerTagType { IMPRISON = "IMPRISON", SYRUP_BOMB = "SYRUP_BOMB", ELECTRIFIED = "ELECTRIFIED", - TELEKINESIS = "TELEKINESIS" + TELEKINESIS = "TELEKINESIS", + COMMANDED = "COMMANDED", + GRUDGE = "GRUDGE", + PSYCHO_SHIFT = "PSYCHO_SHIFT", + ENDURE_TOKEN = "ENDURE_TOKEN", + POWDER = "POWDER", } diff --git a/src/enums/pokemon-anim-type.ts b/src/enums/pokemon-anim-type.ts index 5a0a0c2f622..b153fb2e652 100644 --- a/src/enums/pokemon-anim-type.ts +++ b/src/enums/pokemon-anim-type.ts @@ -12,5 +12,15 @@ export enum PokemonAnimType { * Removes a Pokemon's Substitute doll from the field. * The Pokemon then moves back to its original position. */ - SUBSTITUTE_REMOVE + SUBSTITUTE_REMOVE, + /** + * Brings Tatsugiri and Dondozo to the center of the field, with + * Tatsugiri jumping into the Dondozo's mouth + */ + COMMANDER_APPLY, + /** + * Dondozo "spits out" Tatsugiri, moving Tatsugiri back to its original + * field position. + */ + COMMANDER_REMOVE } diff --git a/src/enums/switch-type.ts b/src/enums/switch-type.ts index 752c0902636..d55872ae83b 100644 --- a/src/enums/switch-type.ts +++ b/src/enums/switch-type.ts @@ -10,5 +10,7 @@ export enum SwitchType { /** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */ BATON_PASS, /** Transfers the returning Pokemon's Substitute to the switched in Pokemon */ - SHED_TAIL + SHED_TAIL, + /** Force switchout to a random party member */ + FORCE_SWITCH, } diff --git a/src/enums/type.ts b/src/enums/type.ts new file mode 100644 index 00000000000..a04849c2ca3 --- /dev/null +++ b/src/enums/type.ts @@ -0,0 +1,22 @@ +export enum Type { + UNKNOWN = -1, + NORMAL = 0, + FIGHTING, + FLYING, + POISON, + GROUND, + ROCK, + BUG, + GHOST, + STEEL, + FIRE, + WATER, + GRASS, + ELECTRIC, + PSYCHIC, + ICE, + DRAGON, + DARK, + FAIRY, + STELLAR +} diff --git a/src/events/arena.ts b/src/events/arena.ts index c05e67d353c..b1126e5c03d 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -1,7 +1,7 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagType } from "#enums/arena-tag-type"; import { TerrainType } from "#app/data/terrain"; -import { WeatherType } from "#app/data/weather"; +import { WeatherType } from "#enums/weather-type"; /** Alias for all {@linkcode ArenaEvent} type strings */ export enum ArenaEventType { diff --git a/src/field/anims.ts b/src/field/anims.ts index c73c52027c5..10198c29005 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -1,6 +1,7 @@ -import BattleScene from "../battle-scene"; -import { PokeballType } from "../data/pokeball"; -import * as Utils from "../utils"; +import BattleScene from "#app/battle-scene"; +import { PokeballType } from "#enums/pokeball"; +import { Variant } from "#app/data/variant"; +import { getFrameMs, randGauss } from "#app/utils"; export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { switch (pokeballType) { @@ -127,7 +128,7 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y: const particleTimer = scene.tweens.addCounter({ repeat: -1, - duration: Utils.getFrameMs(1), + duration: getFrameMs(1), onRepeat: () => { updateParticle(); } @@ -159,7 +160,7 @@ export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.Gam } }); - const dist = Utils.randGauss(25); + const dist = randGauss(25); scene.tweens.add({ targets: particle, x: pokeball.x + dist, @@ -185,3 +186,31 @@ export function sin(index: integer, amplitude: integer): number { export function cos(index: integer, amplitude: integer): number { return amplitude * Math.cos(index * (Math.PI / 128)); } + +/** + * Play the shiny sparkle animation and sound effect for the given sprite + * First ensures that the animation has been properly initialized + * @param sparkleSprite the Sprite to play the animation on + * @param variant which shiny {@linkcode variant} to play the animation for + */ +export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) { + const keySuffix = variant ? `_${variant + 1}` : ""; + const spriteKey = `shiny${keySuffix}`; + const animationKey = `sparkle${keySuffix}`; + + // Make sure the animation exists, and create it if not + if (!scene.anims.exists(animationKey)) { + const frameNames = scene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 }); + scene.anims.create({ + key: `sparkle${keySuffix}`, + frames: frameNames, + frameRate: 32, + showOnStart: true, + hideOnComplete: true, + }); + } + + // Play the animation + sparkleSprite.play(animationKey); + scene.playSound("se/sparkle"); +} diff --git a/src/field/arena.ts b/src/field/arena.ts index abc2b89569c..3cbef659d7a 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -3,9 +3,9 @@ import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPo import { Constructor } from "#app/utils"; import * as Utils from "#app/utils"; import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; -import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "#app/data/weather"; +import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather } from "#app/data/weather"; import { CommonAnim } from "#app/data/battle-anims"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import Move from "#app/data/move"; import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; import { BattlerIndex } from "#app/battle"; @@ -31,6 +31,7 @@ import { Abilities } from "#enums/abilities"; import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; +import { WeatherType } from "#enums/weather-type"; export class Arena { public scene: BattleScene; @@ -706,7 +707,7 @@ export class Arena { case Biome.METROPOLIS: return 141.470; case Biome.FOREST: - return 4.294; + return 0.341; case Biome.SEA: return 0.024; case Biome.SWAMP: diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 12bcace500c..b1b85de9b29 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -1,10 +1,12 @@ import { GameObjects } from "phaser"; -import BattleScene from "../battle-scene"; -import MysteryEncounter from "../data/mystery-encounters/mystery-encounter"; +import BattleScene from "#app/battle-scene"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { Species } from "#enums/species"; import { isNullOrUndefined } from "#app/utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; +import { Variant } from "#app/data/variant"; +import { doShinySparkleAnim } from "#app/field/anims"; type KnownFileRoot = | "arenas" @@ -59,6 +61,10 @@ export class MysteryEncounterSpriteConfig { scale?: number; /** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */ isPokemon?: boolean; + /** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */ + isShiny?: boolean; + /** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */ + variant?: Variant; /** If you are using an item sprite, set to `true` */ isItem?: boolean; /** The sprites alpha. `0` - `1` The lower the number, the more transparent */ @@ -74,6 +80,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con public encounter: MysteryEncounter; public spriteConfigs: MysteryEncounterSpriteConfig[]; public enterFromRight: boolean; + private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[]; constructor(scene: BattleScene, encounter: MysteryEncounter) { super(scene, -72, 76); @@ -86,7 +93,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con }; if (!isNullOrUndefined(result.species)) { - const keys = getSpriteKeysFromSpecies(result.species); + const keys = getSpriteKeysFromSpecies(result.species, undefined, undefined, result.isShiny, result.variant); result.spriteKey = keys.spriteKey; result.fileRoot = keys.fileRoot; result.isPokemon = true; @@ -120,18 +127,36 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con // Sprites with custom X or Y defined will not count for normal spacing requirements const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1)); + this.shinySparkleSprites = []; + const shinySparkleSprites = scene.add.container(0, 0); this.spriteConfigs?.forEach((config) => { - const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha } = config; + const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config; let sprite: GameObjects.Sprite; let tintSprite: GameObjects.Sprite; + let pokemonShinySparkle: Phaser.GameObjects.Sprite | undefined; - if (!isItem) { - sprite = getSprite(spriteKey, hasShadow, yShadow); - tintSprite = getSprite(spriteKey); - } else { + if (isItem) { sprite = getItemSprite(spriteKey, hasShadow, yShadow); tintSprite = getItemSprite(spriteKey); + } else { + sprite = getSprite(spriteKey, hasShadow, yShadow); + tintSprite = getSprite(spriteKey); + if (isPokemon && isShiny) { + // Set Pipeline for shiny variant + sprite.setPipelineData("spriteKey", spriteKey); + tintSprite.setPipelineData("spriteKey", spriteKey); + sprite.setPipelineData("shiny", true); + sprite.setPipelineData("variant", variant); + tintSprite.setPipelineData("shiny", true); + tintSprite.setPipelineData("variant", variant); + // Create Sprite for shiny Sparkle + pokemonShinySparkle = scene.add.sprite(sprite.x, sprite.y, "shiny"); + pokemonShinySparkle.setOrigin(0.5, 1); + pokemonShinySparkle.setVisible(false); + this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 }); + shinySparkleSprites.add(pokemonShinySparkle); + } } sprite.setVisible(!config.hidden); @@ -165,6 +190,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con } } + if (!isNullOrUndefined(pokemonShinySparkle)) { + // Offset the sparkle to match the Pokemon's position + pokemonShinySparkle.setPosition(sprite.x, sprite.y); + } + if (!isNullOrUndefined(alpha)) { sprite.setAlpha(alpha); tintSprite.setAlpha(alpha); @@ -173,6 +203,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.add(sprite); this.add(tintSprite); }); + this.add(shinySparkleSprites); } /** @@ -187,6 +218,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.spriteConfigs.forEach((config) => { if (config.isPokemon) { this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot); + if (config.isShiny) { + this.scene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant); + } } else if (config.isItem) { this.scene.loadAtlas("items", ""); } else { @@ -212,7 +246,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.scene.anims.create({ key: config.spriteKey, frames: frameNames, - frameRate: 12, + frameRate: 10, repeat: -1 }); } @@ -240,11 +274,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.getSprites().map((sprite, i) => { if (!this.spriteConfigs[i].isItem) { sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0); + if (sprite.texture.frameTotal > 1) { + // Show the first animation frame for a smooth transition when the animation starts. + const firstFrame = sprite.texture.frames["0001.png"]; + sprite.setFrame(firstFrame ?? 0); + } } }); this.getTintSprites().map((tintSprite, i) => { if (!this.spriteConfigs[i].isItem) { tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0); + if (tintSprite.texture.frameTotal > 1) { + // Show the first frame for a smooth transition when the animation starts. + const firstFrame = tintSprite.texture.frames["0001.png"]; + tintSprite.setFrame(firstFrame ?? 0); + } } }); @@ -288,6 +332,17 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con return true; } + /** + * Play shiny sparkle animations if there are shiny Pokemon + */ + playShinySparkles() { + for (const sparkleConfig of this.shinySparkleSprites) { + this.scene.time.delayedCall(500, () => { + doShinySparkleAnim(this.scene, sparkleConfig.sprite, sparkleConfig.variant); + }); + } + } + /** * For sprites with animation and that do not have animation disabled, will begin frame animation */ diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e0ed96504b1..fcfc2ff7536 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,36 +3,37 @@ import BattleScene, { AnySound } from "#app/battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; -import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; +import { Constructor, isNullOrUndefined, randSeedInt, type nil } from "#app/utils"; import * as Utils from "#app/utils"; -import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; +import { TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; +import { Type } from "#enums/type"; import { getLevelTotalExp } from "#app/data/exp"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier"; -import { PokeballType } from "#app/data/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; -import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; +import { Status, getRandomStatus } from "#app/data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; -import { WeatherType } from "#app/data/weather"; +import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, PostDamageForceSwitchAbAttr } from "#app/data/ability"; +import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability"; import PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { EVOLVE_MOVE, LevelMoves, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { DamageAchv, achvs } from "#app/system/achv"; import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; -import { Nature, getNatureStatMultiplier } from "#app/data/nature"; +import { getNatureStatMultiplier } from "#app/data/nature"; import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; import { TrainerSlot } from "#app/data/trainer-config"; @@ -50,7 +51,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { FaintPhase } from "#app/phases/faint-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; @@ -66,6 +67,18 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; +import { Nature } from "#enums/nature"; +import { StatusEffect } from "#enums/status-effect"; +import { doShinySparkleAnim } from "#app/field/anims"; + +export enum LearnMoveSituation { + MISC, + LEVEL_UP, + RELEARN, + EVOLUTION, + EVOLUTION_FUSED, // If fusionSpecies has Evolved + EVOLUTION_FUSED_BASE // If fusion's base species has Evolved +} export enum FieldPosition { CENTER, @@ -322,6 +335,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.scene) { return false; } + if (this.switchOutStatus) { + return false; + } return this.scene.field.getIndex(this) > -1; } @@ -424,7 +440,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.anims.create({ key: this.getBattleSpriteKey(), frames: battleFrameNames, - frameRate: 12, + frameRate: 10, repeat: -1 }); } @@ -439,7 +455,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }; if (this.shiny) { const populateVariantColors = (isBackSprite: boolean = false): Promise => { - return new Promise(resolve => { + return new Promise(async resolve => { const battleSpritePath = this.getBattleSpriteAtlasPath(isBackSprite, ignoreOverride).replace("variant/", "").replace(/_[1-3]$/, ""); let config = variantData; const useExpSprite = this.scene.experimentalSprites && this.scene.hasExpSprite(this.getBattleSpriteKey(isBackSprite, ignoreOverride)); @@ -448,7 +464,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variantSet && variantSet[this.variant] === 1) { const cacheKey = this.getBattleSpriteKey(isBackSprite); if (!variantColorCache.hasOwnProperty(cacheKey)) { - this.populateVariantColorCache(cacheKey, useExpSprite, battleSpritePath); + await this.populateVariantColorCache(cacheKey, useExpSprite, battleSpritePath); } } resolve(); @@ -480,10 +496,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param battleSpritePath the filename of the sprite * @param optionalParams any additional params to log */ - fallbackVariantColor(cacheKey: string, attemptedSpritePath: string, useExpSprite: boolean, battleSpritePath: string, ...optionalParams: any[]) { + async fallbackVariantColor(cacheKey: string, attemptedSpritePath: string, useExpSprite: boolean, battleSpritePath: string, ...optionalParams: any[]) { console.warn(`Could not load ${attemptedSpritePath}!`, ...optionalParams); if (useExpSprite) { - this.populateVariantColorCache(cacheKey, false, battleSpritePath); + await this.populateVariantColorCache(cacheKey, false, battleSpritePath); } } @@ -494,18 +510,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param useExpSprite should the experimental sprite be used * @param battleSpritePath the filename of the sprite */ - populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) { + async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) { const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`; - this.scene.cachedFetch(spritePath).then(res => { + return this.scene.cachedFetch(spritePath).then(res => { // Prevent the JSON from processing if it failed to load if (!res.ok) { return this.fallbackVariantColor(cacheKey, res.url, useExpSprite, battleSpritePath, res.status, res.statusText); } return res.json(); }).catch(error => { - this.fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); + return this.fallbackVariantColor(cacheKey, spritePath, useExpSprite, battleSpritePath, error); }).then(c => { - variantColorCache[cacheKey] = c; + if (!isNullOrUndefined(c)) { + variantColorCache[cacheKey] = c; + } }); } @@ -665,21 +683,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } initShinySparkle(): void { - const keySuffix = this.variant ? `_${this.variant + 1}` : ""; - const key = `shiny${keySuffix}`; - const shinySparkle = this.scene.addFieldSprite(0, 0, key); + const shinySparkle = this.scene.addFieldSprite(0, 0, "shiny"); shinySparkle.setVisible(false); shinySparkle.setOrigin(0.5, 1); - const frameNames = this.scene.anims.generateFrameNames(key, { suffix: ".png", end: 34 }); - if (!(this.scene.anims.exists(`sparkle${keySuffix}`))) { - this.scene.anims.create({ - key: `sparkle${keySuffix}`, - frames: frameNames, - frameRate: 32, - showOnStart: true, - hideOnComplete: true, - }); - } this.add(shinySparkle); this.shinySparkle = shinySparkle; @@ -982,7 +988,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.status && this.status.effect === StatusEffect.PARALYSIS) { ret >>= 1; } - if (this.getTag(BattlerTagType.UNBURDEN) && !this.scene.getField(true).some(pokemon => pokemon !== this && pokemon.hasAbilityWithAttr(SuppressFieldAbilitiesAbAttr))) { + if (this.getTag(BattlerTagType.UNBURDEN) && this.hasAbility(Abilities.UNBURDEN)) { ret *= 2; } break; @@ -1067,6 +1073,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.calculateStats(); } + setCustomNature(nature: Nature): void { + this.customPokemonData.nature = nature; + this.calculateStats(); + } + generateNature(naturePool?: Nature[]): void { if (naturePool === undefined) { naturePool = Utils.getEnumValues(Nature); @@ -1563,12 +1574,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the pokemon is trapped */ public isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean { + const commandedTag = this.getTag(BattlerTagType.COMMANDED); + if (commandedTag?.getSourcePokemon(this.scene)?.isActive(true)) { + return true; + } + if (this.isOfType(Type.GHOST)) { return false; } const trappedByAbility = new Utils.BooleanHolder(false); - const opposingField = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + /** + * Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective + * Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger + */ + const opposingFieldUnfiltered = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); opposingField.forEach((opponent) => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated) @@ -1622,7 +1643,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveType = source.getMoveType(move); const typeMultiplier = new Utils.NumberHolder((move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr)) - ? this.getAttackTypeEffectiveness(moveType, source, false, simulated) + ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); @@ -1674,9 +1695,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param source {@linkcode Pokemon} the Pokemon using the move * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @param simulated tag to only apply the strong winds effect message when the move is used + * @param move (optional) the move whose type effectiveness is to be checked. Used for applying {@linkcode VariableMoveTypeChartAttr} * @returns a multiplier for the type effectiveness */ - getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier { + getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, move?: Move): TypeDamageMultiplier { if (moveType === Type.STELLAR) { return this.isTerastallized() ? 2 : 1; } @@ -1695,6 +1717,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let multiplier = types.map(defType => { const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType)); applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); + if (move) { + applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); + } if (source) { const ignoreImmunity = new Utils.BooleanHolder(false); if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) { @@ -1801,40 +1826,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {boolean} includeRelearnerMoves Whether to include moves that would require a relearner. Note the move relearner inherently allows evolution moves * @returns {LevelMoves} A list of moves and the levels they can be learned at */ - getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false): LevelMoves { + getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false, learnSituation: LearnMoveSituation = LearnMoveSituation.MISC): LevelMoves { const ret: LevelMoves = []; let levelMoves: LevelMoves = []; if (!startingLevel) { startingLevel = this.level; } - if (simulateEvolutionChain) { - const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer()); - for (let e = 0; e < evolutionChain.length; e++) { - // TODO: Might need to pass specific form index in simulated evolution chain - const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves(); - if (includeRelearnerMoves) { - levelMoves.push(...speciesLevelMoves); - } else { - levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1])))); - } - } + if (learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies) { // For fusion evolutions, get ONLY the moves of the component mon that evolved + levelMoves = this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0); } else { - levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0); - } - if (this.fusionSpecies) { if (simulateEvolutionChain) { - const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer()); - for (let e = 0; e < fusionEvolutionChain.length; e++) { + const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer()); + for (let e = 0; e < evolutionChain.length; e++) { // TODO: Might need to pass specific form index in simulated evolution chain - const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves(); + const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves(); if (includeRelearnerMoves) { - levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || lm[0] !== 0)); + levelMoves.push(...speciesLevelMoves); } else { - levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1])))); + levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1])))); } } } else { - levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0)); + levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0); + } + if (this.fusionSpecies && learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE) { // For fusion evolutions, get ONLY the moves of the component mon that evolved + if (simulateEvolutionChain) { + const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer()); + for (let e = 0; e < fusionEvolutionChain.length; e++) { + // TODO: Might need to pass specific form index in simulated evolution chain + const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves(); + if (includeRelearnerMoves) { + levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || lm[0] !== EVOLVE_MOVE)); + } else { + levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1])))); + } + } + } else { + levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0)); + } } } levelMoves.sort((lma: [integer, integer], lmb: [integer, integer]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0); @@ -1949,6 +1978,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Function that tries to set a Pokemon shiny based on seed. * For manual use only, usually to roll a Pokemon's shiny chance a second time. + * If it rolls shiny, also sets a random variant and give the Pokemon the associated luck. * * The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536` * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) @@ -1974,6 +2004,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.shiny = randSeedInt(65536) < shinyThreshold.value; if (this.shiny) { + this.variant = this.generateShinyVariant(); + this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.initShinySparkle(); } @@ -2022,15 +2054,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value); const randAbilityIndex = Utils.randSeedInt(2); - const filter = !forStarter ? this.species.getCompatibleFusionSpeciesFilter() - : species => { + const filter = !forStarter ? + this.species.getCompatibleFusionSpeciesFilter() + : (species: PokemonSpecies) => { return pokemonEvolutions.hasOwnProperty(species.speciesId) - && !pokemonPrevolutions.hasOwnProperty(species.speciesId) - && !species.pseudoLegendary - && !species.legendary - && !species.mythical - && !species.isTrainerForbidden() - && species.speciesId !== this.species.speciesId; + && !pokemonPrevolutions.hasOwnProperty(species.speciesId) + && !species.subLegendary + && !species.legendary + && !species.mythical + && !species.isTrainerForbidden() + && species.speciesId !== this.species.speciesId + && species.speciesId !== Species.DITTO; }; let fusionOverride: PokemonSpecies | undefined = undefined; @@ -2101,7 +2135,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (weight === 1 && allMoves[levelMove[1]].power >= 80) { weight = 40; } - if (!movePool.some(m => m[0] === levelMove[1])) { + if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) { movePool.push([ levelMove[1], weight ]); } } @@ -2597,10 +2631,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }; } - // If the attack deals fixed damaged, return a result with that much damage - const fixedDamage = new Utils.IntegerHolder(0); + // If the attack deals fixed damage, return a result with that much damage + const fixedDamage = new Utils.NumberHolder(0); applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { + const multiLensMultiplier = new Utils.NumberHolder(1); + source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier); + fixedDamage.value = Utils.toDmgValue(fixedDamage.value * multiLensMultiplier.value); + return { cancelled: false, result: HitResult.EFFECTIVE, @@ -2630,10 +2668,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const numTargets = multiple ? targets.length : 1; const targetMultiplier = (numTargets > 1) ? 0.75 : 1; - /** 0.25x multiplier if this is an added strike from the attacker's Parental Bond */ - const parentalBondMultiplier = new Utils.NumberHolder(1); + /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ + const multiStrikeEnhancementMultiplier = new Utils.NumberHolder(1); + source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiStrikeEnhancementMultiplier); if (!ignoreSourceAbility) { - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, simulated, numTargets, new Utils.IntegerHolder(0), parentalBondMultiplier); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, simulated, null, multiStrikeEnhancementMultiplier); } /** Doubles damage if this Pokemon's last move was Glaive Rush */ @@ -2710,7 +2749,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { damage.value = Utils.toDmgValue( baseDamage * targetMultiplier - * parentalBondMultiplier.value + * multiStrikeEnhancementMultiplier.value * arenaAttackTypeMultiplier.value * glaiveRushMultiplier.value * criticalMultiplier.value @@ -2836,6 +2875,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // In case of fatal damage, this tag would have gotten cleared before we could lapse it. const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND); + const grudgeTag = this.getTag(BattlerTagType.GRUDGE); const isOneHitKo = result === HitResult.ONE_HIT_KO; @@ -2869,14 +2909,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.turnData.damageTaken += damage; this.battleData.hitCount++; - // Multi-Lens and Parental Bond check for Wimp Out/Emergency Exit - if (this.hasAbilityWithAttr(PostDamageForceSwitchAbAttr)) { - const multiHitModifier = source.getHeldItems().find(m => m instanceof PokemonMultiHitModifier); - if (multiHitModifier || source.hasAbilityWithAttr(AddSecondStrikeAbAttr)) { - applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); - } - } - const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() }; this.turnData.attacksReceived.unshift(attackResult); if (source.isPlayer() && !this.isPlayer()) { @@ -2907,13 +2939,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFainted()) { // set splice index here, so future scene queues happen before FaintedPhase this.scene.setPhaseQueueSplice(); - if (!isNullOrUndefined(destinyTag) && dmg) { - // Destiny Bond will activate during FaintPhase - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, source)); - } else { - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo)); - } + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, grudgeTag, source)); + this.destroySubstitute(); + this.lapseTag(BattlerTagType.COMMANDED); this.resetSummonData(); } @@ -2940,6 +2969,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURING); } else if (this.hp > 1 && this.getTag(BattlerTagType.STURDY)) { surviveDamage.value = this.lapseTag(BattlerTagType.STURDY); + } else if (this.hp >= 1 && this.getTag(BattlerTagType.ENDURE_TOKEN)) { + surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); @@ -2962,6 +2993,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.setPhaseQueueSplice(); this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure)); this.destroySubstitute(); + this.lapseTag(BattlerTagType.COMMANDED); this.resetSummonData(); } return damage; @@ -2977,13 +3009,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage() * @returns integer of damage done */ - damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer { - const damagePhase = new DamagePhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical); + damageAndUpdate(damage: number, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): number { + const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical); this.scene.unshiftPhase(damagePhase); + if (this.switchOutStatus && source) { + damage = 0; + } damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); // Damage amount may have changed, but needed to be queued before calling damage function damagePhase.updateAmount(damage); - applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); + /** + * Run PostDamageAbAttr from any source of damage that is not from a multi-hit + * Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr + */ + if (!source || source.turnData.hitCount <= 1) { + applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source); + } return damage; } @@ -3044,19 +3085,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** @overload */ - getTag(tagType: BattlerTagType): BattlerTag | null; + getTag(tagType: BattlerTagType): BattlerTag | nil; /** @overload */ - getTag(tagType: Constructor): T | null; + getTag(tagType: Constructor): T | nil; - getTag(tagType: BattlerTagType | Constructor): BattlerTag | null { + getTag(tagType: BattlerTagType | Constructor): BattlerTag | nil { if (!this.summonData) { return null; } return (tagType instanceof Function ? this.summonData.tags.find(t => t instanceof tagType) : this.summonData.tags.find(t => t.tagType === tagType) - )!; // TODO: is this bang correct? + ); } findTag(tagFilter: ((tag: BattlerTag) => boolean)) { @@ -3074,6 +3115,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } lapseTag(tagType: BattlerTagType): boolean { + if (!this.summonData) { + return false; + } const tags = this.summonData.tags; const tag = tags.find(t => t.tagType === tagType); if (tag && !(tag.lapse(this, BattlerTagLapseType.CUSTOM))) { @@ -3084,6 +3128,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } lapseTags(lapseType: BattlerTagLapseType): void { + if (!this.summonData) { + return; + } const tags = this.summonData.tags; tags.filter(t => lapseType === BattlerTagLapseType.FAINT || ((t.lapseTypes.some(lType => lType === lapseType)) && !(t.lapse(this, lapseType)))).forEach(t => { t.onRemove(this); @@ -3092,6 +3139,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } removeTag(tagType: BattlerTagType): boolean { + if (!this.summonData) { + return false; + } const tags = this.summonData.tags; const tag = tags.find(t => t.tagType === tagType); if (tag) { @@ -3207,18 +3257,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return null; } - getMoveHistory(): TurnMove[] { + public getMoveHistory(): TurnMove[] { return this.battleSummonData.moveHistory; } - pushMoveHistory(turnMove: TurnMove) { + public pushMoveHistory(turnMove: TurnMove): void { + if (!this.isOnField()) { + return; + } turnMove.turn = this.scene.currentBattle?.turn; this.getMoveHistory().push(turnMove); } - getLastXMoves(turnCount: integer = 0): TurnMove[] { + /** + * Returns a list of the most recent move entries in this Pokemon's move history. + * The retrieved move entries are sorted in order from NEWEST to OLDEST. + * @param moveCount The number of move entries to retrieve. + * If negative, retrieve the Pokemon's entire move history (equivalent to reversing the output of {@linkcode getMoveHistory()}). + * Default is `1`. + * @returns A list of {@linkcode TurnMove}, as specified above. + */ + getLastXMoves(moveCount: number = 1): TurnMove[] { const moveHistory = this.getMoveHistory(); - return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse(); + if (moveCount >= 0) { + return moveHistory.slice(Math.max(moveHistory.length - moveCount, 0)).reverse(); + } else { + return moveHistory.slice(0).reverse(); + } } getMoveQueue(): QueuedMove[] { @@ -3436,12 +3501,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE); } - canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null): boolean { + /** + * Checks if a status effect can be applied to the Pokemon. + * + * @param effect The {@linkcode StatusEffect} whose applicability is being checked + * @param quiet Whether in-battle messages should trigger or not + * @param overrideStatus Whether the Pokemon's current status can be overriden + * @param sourcePokemon The Pokemon that is setting the status effect + * @param ignoreField Whether any field effects (weather, terrain, etc.) should be considered + */ + canSetStatus(effect: StatusEffect | undefined, quiet: boolean = false, overrideStatus: boolean = false, sourcePokemon: Pokemon | null = null, ignoreField: boolean = false): boolean { if (effect !== StatusEffect.FAINT) { if (overrideStatus ? this.status?.effect === effect : this.status) { return false; } - if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.MISTY) { + if (this.isGrounded() && (!ignoreField && this.scene.arena.terrain?.terrainType === TerrainType.MISTY)) { return false; } } @@ -3491,7 +3565,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case StatusEffect.FREEZE: - if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { + if (this.isOfType(Type.ICE) || (!ignoreField && (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType)))) { return false; } break; @@ -3581,7 +3655,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.status = null; if (lastStatus === StatusEffect.SLEEP) { - this.setFrameRate(12); + this.setFrameRate(10); if (this.getTag(BattlerTagType.NIGHTMARE)) { this.lapseTag(BattlerTagType.NIGHTMARE); } @@ -3639,6 +3713,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.triggerPokemonBattleAnim(this, PokemonAnimType.SUBSTITUTE_ADD); this.getTag(SubstituteTag)!.sourceInFocus = false; } + + // If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite. + if (this.hasAbilityWithAttr(CommanderAbAttr) + && this.scene.currentBattle.double + && this.getAlly()?.species.speciesId === Species.DONDOZO) { + this.setVisible(false); + } this.summonDataPrimer = null; } this.updateInfo(); @@ -3669,8 +3750,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { setFrameRate(frameRate: integer) { this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; - this.getSprite().play(this.getBattleSpriteKey()); - this.getTintSprite()?.play(this.getBattleSpriteKey()); + try { + this.getSprite().play(this.getBattleSpriteKey()); + } catch (err: unknown) { + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); + } + try { + this.getTintSprite()?.play(this.getBattleSpriteKey()); + } catch (err: unknown) { + console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err); + } } tint(color: number, alpha?: number, duration?: integer, ease?: string) { @@ -3735,8 +3824,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sparkle(): void { if (this.shinySparkle) { - this.shinySparkle.play(`sparkle${this.variant ? `_${this.variant + 1}` : ""}`); - this.scene.playSound("se/sparkle"); + doShinySparkleAnim(this.scene, this.shinySparkle, this.variant); } } @@ -4022,8 +4110,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.resetTurnData(); if (clearEffects) { this.destroySubstitute(); - this.resetSummonData(); - this.resetBattleData(); + this.resetSummonData(); // this also calls `resetBattleSummonData` } if (hideInfo) { this.hideInfo(); @@ -4072,6 +4159,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } return false; } + + /** + * Reduces one of this Pokemon's held item stacks by 1, and removes the item if applicable. + * Does nothing if this Pokemon is somehow not the owner of the held item. + * @param heldItem The item stack to be reduced by 1. + * @param forBattle If `false`, do not trigger in-battle effects (such as Unburden) from losing the item. For example, set this to `false` if the Pokemon is giving away the held item for a Mystery Encounter. Default is `true`. + * @returns `true` if the item was removed successfully, `false` otherwise. + */ + public loseHeldItem(heldItem: PokemonHeldItemModifier, forBattle: boolean = true): boolean { + if (heldItem.pokemonId === -1 || heldItem.pokemonId === this.id) { + heldItem.stackCount--; + if (heldItem.stackCount <= 0) { + this.scene.removeModifier(heldItem, !this.isPlayer()); + } + if (forBattle) { + applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); + } + return true; + } else { + return false; + } + } } export default interface Pokemon { @@ -4192,28 +4301,29 @@ export class PlayerPokemon extends Pokemon { }); } - addFriendship(friendship: integer): void { - const starterSpeciesId = this.species.getRootSpeciesId(); - const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0; - const starterData = [ - this.scene.gameData.starterData[starterSpeciesId], - fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null - ].filter(d => !!d); - const amount = new Utils.IntegerHolder(friendship); - let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; - if (this.scene.eventManager.isEventActive()) { - candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier(); - } - const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); - if (amount.value > 0) { + addFriendship(friendship: number): void { + if (friendship > 0) { + const starterSpeciesId = this.species.getRootSpeciesId(); + const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0; + const starterData = [ + this.scene.gameData.starterData[starterSpeciesId], + fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null + ].filter(d => !!d); + const amount = new Utils.NumberHolder(friendship); this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); - this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount); + let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; + if (this.scene.eventManager.isEventActive()) { + candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier(); + } + const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (this.scene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); + // Add friendship to this PlayerPokemon this.friendship = Math.min(this.friendship + amount.value, 255); if (this.friendship === 255) { this.scene.validateAchv(achvs.MAX_FRIENDSHIP); } - starterData.forEach((sd: StarterDataEntry, i: integer) => { + // Add to candy progress for this mon's starter species and its fused species (if it has one) + starterData.forEach((sd: StarterDataEntry, i: number) => { const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species; sd.friendship = (sd.friendship || 0) + starterAmount.value; if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) { @@ -4222,10 +4332,8 @@ export class PlayerPokemon extends Pokemon { } }); } else { - this.friendship = Math.max(this.friendship + amount.value, 0); - for (const sd of starterData) { - sd.friendship = Math.max((sd.friendship || 0) + starterAmount.value, 0); - } + // Lose friendship upon fainting + this.friendship = Math.max(this.friendship + friendship, 0); } } /** @@ -4514,7 +4622,7 @@ export class PlayerPokemon extends Pokemon { && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const transferModifiers: Promise[] = []; for (const modifier of fusedPartyMemberHeldModifiers) { - transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true)); + transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false)); } Promise.allSettled(transferModifiers).then(() => { this.scene.updateModifiers(true, true).then(() => { @@ -4558,12 +4666,13 @@ export class EnemyPokemon extends Pokemon { public aiType: AiType; public bossSegments: integer; public bossSegmentIndex: integer; - /** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */ + /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ public readonly isPopulatedFromDataSource: boolean; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) { - super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, - dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource); + constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) { + super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender, + (!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined, + undefined, dataSource ? dataSource.nature : undefined, dataSource); this.trainerSlot = trainerSlot; this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource @@ -4592,12 +4701,15 @@ export class EnemyPokemon extends Pokemon { if (!dataSource) { this.generateAndPopulateMoveset(); - this.trySetShiny(); - if (Overrides.OPP_SHINY_OVERRIDE) { + if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) { + this.shiny = false; + } else { + this.trySetShiny(); + } + + if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); - } else if (Overrides.OPP_SHINY_OVERRIDE === false) { - this.shiny = false; } if (this.shiny) { @@ -5075,26 +5187,6 @@ export class EnemyPokemon extends Pokemon { } } - heal(amount: integer): integer { - if (this.isBoss()) { - const amountRatio = amount / this.getMaxHp(); - const segmentBypassCount = Math.floor(amountRatio / (1 / this.bossSegments)); - const segmentSize = this.getMaxHp() / this.bossSegments; - for (let s = 1; s < this.bossSegments; s++) { - const hpThreshold = segmentSize * s; - if (this.hp <= Math.round(hpThreshold)) { - const healAmount = Math.min(amount, this.getMaxHp() - this.hp, Math.round(hpThreshold + (segmentSize * segmentBypassCount) - this.hp)); - this.hp += healAmount; - return healAmount; - } else if (s >= this.bossSegmentIndex) { - return super.heal(amount); - } - } - } - - return super.heal(amount); - } - getFieldIndex(): integer { return this.scene.getEnemyField().indexOf(this); } @@ -5105,7 +5197,7 @@ export class EnemyPokemon extends Pokemon { /** * Add a new pokemon to the player's party (at `slotIndex` if set). - * If the first slot is replaced, the new pokemon's visibility will be set to `false`. + * The new pokemon's visibility will be set to `false`. * @param pokeballType the type of pokeball the pokemon was caught with * @param slotIndex an optional index to place the pokemon in the party * @returns the pokemon that was added or null if the pokemon could not be added @@ -5123,14 +5215,14 @@ export class EnemyPokemon extends Pokemon { const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); if (Utils.isBetween(slotIndex, 0, PLAYER_PARTY_MAX_SIZE - 1)) { - if (slotIndex === 0) { - newPokemon.setVisible(false); // Hide if replaced with first pokemon - } party.splice(slotIndex, 0, newPokemon); } else { party.push(newPokemon); } + // Hide the Pokemon since it is not on the field + newPokemon.setVisible(false); + ret = newPokemon; this.scene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); } @@ -5202,6 +5294,7 @@ export class PokemonBattleSummonData { export class PokemonTurnData { public flinched: boolean = false; public acted: boolean = false; + /** How many times the move should hit the target(s) */ public hitCount: number = 0; /** * - `-1` = Calculate how many hits are left @@ -5220,6 +5313,11 @@ export class PokemonTurnData { public switchedInThisTurn: boolean = false; public failedRunAway: boolean = false; public joinedRound: boolean = false; + /** + * Used to make sure multi-hits occur properly when the user is + * forced to act again in the same turn + */ + public extraTurns: number = 0; } export enum AiType { diff --git a/src/game-mode.ts b/src/game-mode.ts index 8f1bb9356e6..3f12c5b056e 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -230,13 +230,20 @@ export class GameMode implements GameModeConfig { return waveIndex % 10 === 0; } + /** + * @returns `true` if the current battle is against classic mode's final boss + */ + isBattleClassicFinalBoss(waveIndex: number): boolean { + return (this.modeId === GameModes.CLASSIC || this.modeId === GameModes.CHALLENGE) && this.isWaveFinal(waveIndex); + } + /** * Every 50 waves of an Endless mode is a boss * At this time it is paradox pokemon * @returns true if waveIndex is a multiple of 50 in Endless */ isEndlessBoss(waveIndex: integer): boolean { - return !!(waveIndex % 50) && + return waveIndex % 50 === 0 && (this.modeId === GameModes.ENDLESS || this.modeId === GameModes.SPLICED_ENDLESS); } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index dfa46ce3667..57b3ced1813 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -3,30 +3,31 @@ import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evol import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { allMoves, AttackMove, selfStatLowerMoves } from "#app/data/move"; -import { getNatureName, getNatureStatMultiplier, Nature } from "#app/data/nature"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; +import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; -import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect"; -import { Type } from "#app/data/type"; +import { getStatusEffectDescriptor } from "#app/data/status-effect"; +import { Type } from "#enums/type"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; -import { - AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier -} from "#app/modifier/modifier"; +import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; import { Unlockables } from "#app/system/unlockables"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import { getModifierTierTextTint } from "#app/ui/text"; -import { formatMoney, getEnumKeys, getEnumValues, IntegerHolder, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; +import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; +import { Nature } from "#enums/nature"; +import { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; import { SpeciesFormKey } from "#enums/species-form-key"; import { getStatKey, PermanentStat, Stat, TEMP_BATTLE_STATS, TempBattleStat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; const outputModifierData = false; @@ -730,7 +731,7 @@ export class MoneyRewardModifierType extends ModifierType { } getDescription(scene: BattleScene): string { - const moneyAmount = new IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); + const moneyAmount = new NumberHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); const formattedMoney = formatMoney(scene.moneyFormat, moneyAmount.value); @@ -1553,6 +1554,7 @@ export const modifierTypes = { SHINY_CHARM: () => new ModifierType("modifierType:ModifierType.SHINY_CHARM", "shiny_charm", (type, _args) => new ShinyRateBoosterModifier(type)), ABILITY_CHARM: () => new ModifierType("modifierType:ModifierType.ABILITY_CHARM", "ability_charm", (type, _args) => new HiddenAbilityRateBoosterModifier(type)), + CATCHING_CHARM: () => new ModifierType("modifierType:ModifierType.CATCHING_CHARM", "catching_charm", (type, _args) => new CriticalCatchChanceBoosterModifier(type)), IV_SCANNER: () => new ModifierType("modifierType:ModifierType.IV_SCANNER", "scanner", (type, _args) => new IvScannerModifier(type)), @@ -1619,19 +1621,21 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.POKEBALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.POKEBALL)) ? 0 : 6, 6), new WeightedModifierType(modifierTypes.RARE_CANDY, 2), new WeightedModifierType(modifierTypes.POTION, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 10 || p.getHpRatio() <= 0.875) && !p.isFainted()).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875) && !p.isFainted()).length, 3); return thresholdPartyMemberCount * 3; }, 9), new WeightedModifierType(modifierTypes.SUPER_POTION, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 25 || p.getHpRatio() <= 0.75) && !p.isFainted()).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 25 && p.getHpRatio() <= 0.75) && !p.isFainted()).length, 3); return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.ETHER, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) + && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)).length).length, 3); return thresholdPartyMemberCount * 3; }, 9), new WeightedModifierType(modifierTypes.MAX_ETHER, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) + && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)).length).length, 3); return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), @@ -1665,11 +1669,11 @@ const modifierPool: ModifierPool = { return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0; }, 1), new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625) && !p.isFainted()).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 100 && p.getHpRatio() <= 0.625) && !p.isFainted()).length, 3); return thresholdPartyMemberCount * 3; }, 9), new WeightedModifierType(modifierTypes.MAX_POTION, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3); return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.FULL_RESTORE, (party: Pokemon[]) => { @@ -1679,15 +1683,17 @@ const modifierPool: ModifierPool = { } return false; })).length, 3); - const thresholdPartyMemberCount = Math.floor((Math.min(party.filter(p => (p.getInverseHp() >= 150 || p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3) + statusEffectPartyMemberCount) / 2); + const thresholdPartyMemberCount = Math.floor((Math.min(party.filter(p => (p.getInverseHp() >= 100 && p.getHpRatio() <= 0.5) && !p.isFainted()).length, 3) + statusEffectPartyMemberCount) / 2); return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.ELIXIR, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) + && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)).length).length, 3); return thresholdPartyMemberCount * 3; }, 9), new WeightedModifierType(modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { - const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed >= Math.floor(m.getMovePp() / 2)).length).length, 3); + const thresholdPartyMemberCount = Math.min(party.filter(p => p.hp && !p.getHeldItems().some(m => m instanceof BerryModifier && m.berryType === BerryType.LEPPA) + && p.getMoveset().filter(m => m?.ppUsed && (m.getMovePp() - m.ppUsed) <= 5 && m.ppUsed > Math.floor(m.getMovePp() / 2)).length).length, 3); return thresholdPartyMemberCount; }, 3), new WeightedModifierType(modifierTypes.DIRE_HIT, 4), @@ -1696,10 +1702,8 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => { return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8); }, 8), - new WeightedModifierType(modifierTypes.MAP, - (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? party[0].scene.eventManager.isEventActive() ? 2 : 1 : 0, - (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 2 : 1), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 3 : 0), + new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 2 : 0, 2), + new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), new WeightedModifierType(modifierTypes.TM_GREAT, 3), new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { if (!party.find(p => p.getLearnableLevelMoves().length)) { @@ -1727,8 +1731,14 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { const { gameMode, gameData } = party[0].scene; if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { - return party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) - && !p.getHeldItems().some(i => i instanceof EvolutionStatBoosterModifier) && !p.isMax()) ? 10 : 0; + return party.some(p => { + // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd + if (!p.isMax() && ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions)))) { + // Check if Pokemon is already holding an Eviolite + return !p.getHeldItems().some(i => i.type.id === "EVIOLITE"); + } + return false; + }) ? 10 : 0; } return 0; }), @@ -1741,19 +1751,59 @@ const modifierPool: ModifierPool = { || (p.isFusion() && checkedSpecies.includes(p.getFusionSpeciesForm(true).speciesId)))) ? 12 : 0; }, 12), new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { - const checkedAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ]; - const checkedMoves = [ Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT ]; - // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear - return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier) - && (checkedAbilities.some(a => p.hasAbility(a, false, true)) - || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0; + return party.some(p => { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves from comment they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + // Abilities that take advantage of obtaining the actual status effect + const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + + if (!isHoldingOrb) { + if (canSetStatus) { + return hasRelevantAbilities || hasStatusMoves; + } else { + return hasItemMoves; + } + } + return false; + }) ? 10 : 0; }, 10), new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { - const checkedAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ]; - const checkedMoves = [ Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT ]; - // If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear - return party.some(p => !p.getHeldItems().some(i => i instanceof TurnStatusEffectModifier) - && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => m && checkedMoves.includes(m.moveId)))) ? 10 : 0; + return party.some(p => { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); + const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves from comment they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + // Abilities that take advantage of obtaining the actual status effect + const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + + if (!isHoldingOrb) { + if (canSetStatus) { + return hasRelevantAbilities || hasStatusMoves; + } else { + return hasItemMoves; + } + } + return false; + }) ? 10 : 0; }, 10), new WeightedModifierType(modifierTypes.WHITE_HERB, (party: Pokemon[]) => { const checkedAbilities = [ Abilities.WEAK_ARMOR, Abilities.CONTRARY, Abilities.MOODY, Abilities.ANGER_SHELL, Abilities.COMPETITIVE, Abilities.DEFIANT ]; @@ -1767,7 +1817,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), new WeightedModifierType(modifierTypes.TM_ULTRA, 11), - new WeightedModifierType(modifierTypes.RARER_CANDY, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 6 : 4), + new WeightedModifierType(modifierTypes.RARER_CANDY, 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), @@ -1790,7 +1840,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), - new WeightedModifierType(modifierTypes.SOOTHE_BELL, (party: Pokemon[]) => party[0].scene.eventManager.isEventActive() ? 0 : 4), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4), new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0891262649c..05d9e8b9897 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,12 +1,11 @@ import type BattleScene from "#app/battle-scene"; -import { FusionSpeciesFormEvolution, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/move"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { type FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; -import { Type } from "#app/data/type"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; @@ -19,18 +18,20 @@ import type { VoucherType } from "#app/system/voucher"; import { Command } from "#app/ui/command-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils"; -import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; +import { Moves } from "#enums/moves"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; +import { Type } from "#enums/type"; import i18next from "i18next"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; +import { applyAbAttrs, CommanderAbAttr } from "#app/data/ability"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -724,22 +725,6 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return 1; } - //Applies to items with chance of activating secondary effects ie Kings Rock - getSecondaryChanceMultiplier(pokemon: Pokemon): number { - // Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock - if (!pokemon.getLastXMoves(0)[0]) { - return 1; - } - const sheerForceAffected = allMoves[pokemon.getLastXMoves(0)[0].move].chance >= 0 && pokemon.hasAbility(Abilities.SHEER_FORCE); - - if (sheerForceAffected) { - return 0; - } else if (pokemon.hasAbility(Abilities.SERENE_GRACE)) { - return 2; - } - return 1; - } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { const pokemon = this.getPokemon(scene); if (!pokemon) { @@ -1612,9 +1597,16 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { } } +/** + * Class for Pokemon held items like King's Rock + * Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from Abilities.SERENE_GRACE + */ export class FlinchChanceModifier extends PokemonHeldItemModifier { + private chance: number; constructor(type: ModifierType, pokemonId: number, stackCount?: number) { super(type, pokemonId, stackCount); + + this.chance = 10; } matchType(modifier: Modifier) { @@ -1642,7 +1634,8 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { * @returns `true` if {@linkcode FlinchChanceModifier} has been applied */ override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean { - if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) { + // The check for pokemon.battleSummonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch + if (pokemon.battleSummonData && !flinched.value && pokemon.randSeedInt(100) < (this.getStackCount() * this.chance)) { flinched.value = true; return true; } @@ -1650,7 +1643,7 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier { return false; } - getMaxHeldItemCount(pokemon: Pokemon): number { + getMaxHeldItemCount(_pokemon: Pokemon): number { return 3; } } @@ -1937,10 +1930,16 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { * @returns always `true` */ override apply(pokemon: Pokemon): boolean { + // Restore the Pokemon to half HP pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); + // Remove the Pokemon's FAINT status pokemon.resetStatus(true, false, true); + + // Reapply Commander on the Pokemon's side of the field, if applicable + const field = pokemon.isPlayer() ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField(); + field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false)); return true; } @@ -2189,14 +2188,8 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { * @returns */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.customPokemonData.nature = this.nature; - let speciesId = playerPokemon.species.speciesId; - playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); - - while (pokemonPrevolutions.hasOwnProperty(speciesId)) { - speciesId = pokemonPrevolutions[speciesId]; - playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); - } + playerPokemon.setCustomNature(this.nature); + playerPokemon.scene.gameData.unlockSpeciesNature(playerPokemon.species, this.nature); return true; } @@ -2682,32 +2675,65 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { } /** - * Applies {@linkcode PokemonMultiHitModifier} - * @param _pokemon The {@linkcode Pokemon} using the move - * @param count {@linkcode NumberHolder} holding the number of items - * @param power {@linkcode NumberHolder} holding the power of the move + * For each stack, converts 25 percent of attack damage into an additional strike. + * @param pokemon The {@linkcode Pokemon} using the move + * @param moveId The {@linkcode Moves | identifier} for the move being used + * @param count {@linkcode NumberHolder} holding the move's hit count for this turn + * @param damageMultiplier {@linkcode NumberHolder} holding a damage multiplier applied to a strike of this move * @returns always `true` */ - override apply(_pokemon: Pokemon, count: NumberHolder, power: NumberHolder): boolean { - count.value *= (this.getStackCount() + 1); - - switch (this.getStackCount()) { - case 1: - power.value *= 0.4; - break; - case 2: - power.value *= 0.25; - break; - case 3: - power.value *= 0.175; - break; + override apply(pokemon: Pokemon, moveId: Moves, count: NumberHolder | null = null, damageMultiplier: NumberHolder | null = null): boolean { + const move = allMoves[moveId]; + /** + * The move must meet Parental Bond's restrictions for this item + * to apply. This means + * - Only attacks are boosted + * - Multi-strike moves, charge moves, and self-sacrificial moves are not boosted + * (though Multi-Lens can still affect moves boosted by Parental Bond) + * - Multi-target moves are not boosted *unless* they can only hit a single Pokemon + * - Fling, Uproar, Rollout, Ice Ball, and Endeavor are not boosted + */ + if (!move.canBeMultiStrikeEnhanced(pokemon)) { + return false; } + if (!isNullOrUndefined(count)) { + return this.applyHitCountBoost(count); + } else if (!isNullOrUndefined(damageMultiplier)) { + return this.applyDamageModifier(pokemon, damageMultiplier); + } + + return false; + } + + /** Adds strikes to a move equal to the number of stacked Multi-Lenses */ + private applyHitCountBoost(count: NumberHolder): boolean { + count.value += this.getStackCount(); return true; } + /** + * If applied to the first hit of a move, sets the damage multiplier + * equal to (1 - the number of stacked Multi-Lenses). + * Additional strikes beyond that are given a 0.25x damage multiplier + */ + private applyDamageModifier(pokemon: Pokemon, damageMultiplier: NumberHolder): boolean { + if (pokemon.turnData.hitsLeft === pokemon.turnData.hitCount) { + // Reduce first hit by 25% for each stack count + damageMultiplier.value *= 1 - 0.25 * this.getStackCount(); + return true; + } else if (pokemon.turnData.hitCount - pokemon.turnData.hitsLeft !== this.getStackCount() + 1) { + // Deal 25% damage for each remaining Multi Lens hit + damageMultiplier.value *= 0.25; + return true; + } else { + // An extra hit not caused by Multi Lens -- assume it is Parental Bond + return false; + } + } + getMaxHeldItemCount(pokemon: Pokemon): number { - return 3; + return 2; } } @@ -2785,7 +2811,7 @@ export class MoneyRewardModifier extends ConsumableModifier { battleScene.getPlayerParty().map(p => { if (p.species?.speciesId === Species.GIMMIGHOUL || p.fusionSpecies?.speciesId === Species.GIMMIGHOUL) { - p.evoCounter ? p.evoCounter++ : p.evoCounter = 1; + p.evoCounter ? p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3) : p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3); const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(p) as EvoTrackerModifier; battleScene.addModifier(modifier); } @@ -2950,6 +2976,38 @@ export class ShinyRateBoosterModifier extends PersistentModifier { } } +export class CriticalCatchChanceBoosterModifier extends PersistentModifier { + constructor(type: ModifierType, stackCount?: number) { + super(type, stackCount); + } + + match(modifier: Modifier): boolean { + return modifier instanceof CriticalCatchChanceBoosterModifier; + } + + clone(): CriticalCatchChanceBoosterModifier { + return new CriticalCatchChanceBoosterModifier(this.type, this.stackCount); + } + + /** + * Applies {@linkcode CriticalCatchChanceBoosterModifier} + * @param boost {@linkcode NumberHolder} holding the boost value + * @returns always `true` + */ + override apply(boost: NumberHolder): boolean { + // 1 stack: 2x + // 2 stack: 2.5x + // 3 stack: 3x + boost.value *= 1.5 + this.getStackCount() / 2; + + return true; + } + + getMaxStackCount(scene: BattleScene): number { + return 3; + } +} + export class LockModifierTiersModifier extends PersistentModifier { constructor(type: ModifierType, stackCount?: number) { super(type, stackCount); @@ -3566,7 +3624,7 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { super(type, stackCount || 10); //Hardcode temporarily - this.chance = .02; + this.chance = 2; } match(modifier: Modifier) { @@ -3574,24 +3632,24 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { } clone() { - return new EnemyEndureChanceModifier(this.type, this.chance * 100, this.stackCount); + return new EnemyEndureChanceModifier(this.type, this.chance, this.stackCount); } getArgs(): any[] { - return [ this.chance * 100 ]; + return [ this.chance ]; } /** - * Applies {@linkcode EnemyEndureChanceModifier} - * @param target {@linkcode Pokemon} to apply the {@linkcode BattlerTagType.ENDURING} chance to + * Applies a chance of enduring a lethal hit of an attack + * @param target the {@linkcode Pokemon} to apply the {@linkcode BattlerTagType.ENDURING} chance to * @returns `true` if {@linkcode Pokemon} endured */ override apply(target: Pokemon): boolean { - if (target.battleData.endured || Phaser.Math.RND.realInRange(0, 1) >= (this.chance * this.getStackCount())) { + if (target.battleData.endured || target.randSeedInt(100) >= (this.chance * this.getStackCount())) { return false; } - target.addTag(BattlerTagType.ENDURING, 1); + target.addTag(BattlerTagType.ENDURE_TOKEN, 1); target.battleData.endured = true; diff --git a/src/overrides.ts b/src/overrides.ts index d7a8ee18f15..85be47d95cc 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -47,7 +47,18 @@ class DefaultOverrides { /** a specific seed (default: a random string of 24 characters) */ readonly SEED_OVERRIDE: string = ""; readonly WEATHER_OVERRIDE: WeatherType = WeatherType.NONE; - readonly BATTLE_TYPE_OVERRIDE: "double" | "single" | null = null; + /** + * If `null`, ignore this override. + * + * If `"single"`, set every non-trainer battle to be a single battle. + * + * If `"double"`, set every battle (including trainer battles) to be a double battle. + * + * If `"even-doubles"`, follow the `"double"` rule on even wave numbers, and follow the `"single"` rule on odd wave numbers. + * + * If `"odd-doubles"`, follow the `"double"` rule on odd wave numbers, and follow the `"single"` rule on even wave numbers. + */ + readonly BATTLE_TYPE_OVERRIDE: BattleStyle | null = null; readonly STARTING_WAVE_OVERRIDE: number = 0; readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; @@ -75,6 +86,8 @@ class DefaultOverrides { readonly ITEM_UNLOCK_OVERRIDE: Unlockables[] = []; /** Set to `true` to show all tutorials */ readonly BYPASS_TUTORIAL_SKIP_OVERRIDE: boolean = false; + /** Set to `true` to be able to re-earn already unlocked achievements */ + readonly ACHIEVEMENTS_REUNLOCK_OVERRIDE: boolean = false; /** Set to `true` to force Paralysis and Freeze to always activate, or `false` to force them to not activate */ readonly STATUS_ACTIVATION_OVERRIDE: boolean | null = null; @@ -164,7 +177,11 @@ class DefaultOverrides { // MYSTERY ENCOUNTER OVERRIDES // ------------------------- - /** 1 to 256, set to null to ignore */ + /** + * `1` (almost never) to `256` (always), set to `null` to disable the override + * + * Note: Make sure `STARTING_WAVE_OVERRIDE > 10`, otherwise MEs won't trigger + */ readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null; readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null; @@ -229,3 +246,5 @@ export default { ...defaultOverrides, ...overrides } satisfies InstanceType; + +export type BattleStyle = "double" | "single" | "even-doubles" | "odd-doubles"; diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 483e6eac943..de10d1eca45 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { SubstituteTag } from "#app/data/battler-tags"; -import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball"; +import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, getCriticalCaptureChance } from "#app/data/pokeball"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import { EnemyPokemon } from "#app/field/pokemon"; @@ -52,8 +52,10 @@ export class AttemptCapturePhase extends PokemonPhase { const catchRate = pokemon.species.catchRate; const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType); const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; - const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); - const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x))); + const modifiedCatchRate = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); + const shakeProbability = Math.round(65536 / Math.pow((255 / modifiedCatchRate), 0.1875)); // Formula taken from gen 6 + const criticalCaptureChance = getCriticalCaptureChance(this.scene, modifiedCatchRate); + const isCritical = pokemon.randSeedInt(256) < criticalCaptureChance; const fpOffset = pokemon.getFieldPositionOffset(); const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); @@ -61,17 +63,19 @@ export class AttemptCapturePhase extends PokemonPhase { this.pokeball.setOrigin(0.5, 0.625); this.scene.field.add(this.pokeball); - this.scene.playSound("se/pb_throw"); + this.scene.playSound("se/pb_throw", isCritical ? { rate: 0.2 } : undefined); // Crit catch throws are higher pitched this.scene.time.delayedCall(300, () => { this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); }); this.scene.tweens.add({ + // Throw animation targets: this.pokeball, x: { value: 236 + fpOffset[0], ease: "Linear" }, y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, duration: 500, onComplete: () => { + // Ball opens this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); this.scene.playSound("se/pb_rel"); @@ -80,30 +84,33 @@ export class AttemptCapturePhase extends PokemonPhase { addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); this.scene.tweens.add({ + // Mon enters ball targets: pokemon, duration: 500, ease: "Sine.easeIn", scale: 0.25, y: 20, onComplete: () => { + // Ball closes this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); pokemon.setVisible(false); this.scene.playSound("se/pb_catch"); this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); const doShake = () => { + // After the overall catch rate check, the game does 3 shake checks before confirming the catch. let shakeCount = 0; const pbX = this.pokeball.x; const shakeCounter = this.scene.tweens.addCounter({ from: 0, to: 1, - repeat: 4, + repeat: isCritical ? 2 : 4, // Critical captures only perform 1 shake check yoyo: true, ease: "Cubic.easeOut", duration: 250, repeatDelay: 500, onUpdate: t => { - if (shakeCount && shakeCount < 4) { + if (shakeCount && shakeCount < (isCritical ? 2 : 4)) { const value = t.getValue(); const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1; this.pokeball.setX(pbX + value * 4 * directionMultiplier); @@ -114,13 +121,18 @@ export class AttemptCapturePhase extends PokemonPhase { if (!pokemon.species.isObtainable()) { shakeCounter.stop(); this.failCatch(shakeCount); - } else if (shakeCount++ < 3) { - if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { + } else if (shakeCount++ < (isCritical ? 1 : 3)) { + // Shake check (skip check for critical or guaranteed captures, but still play the sound) + if (pokeballMultiplier === -1 || isCritical || modifiedCatchRate >= 255 || pokemon.randSeedInt(65536) < shakeProbability) { this.scene.playSound("se/pb_move"); } else { shakeCounter.stop(); this.failCatch(shakeCount); } + } else if (isCritical && pokemon.randSeedInt(65536) >= shakeProbability) { + // Above, perform the one shake check for critical captures after the ball shakes once + shakeCounter.stop(); + this.failCatch(shakeCount); } else { this.scene.playSound("se/pb_lock"); addPokeballCaptureStars(this.scene, this.pokeball); @@ -153,7 +165,8 @@ export class AttemptCapturePhase extends PokemonPhase { }); }; - this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake)); + // Ball bounces (handled in pokemon.ts) + this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake, isCritical)); } }); } diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index b4768dc9a26..109fc5b582d 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -52,7 +52,7 @@ export class AttemptRunPhase extends PokemonPhase { enemyPokemon.trySetStatus(StatusEffect.FAINT); }); - this.scene.pushPhase(new BattleEndPhase(this.scene)); + this.scene.pushPhase(new BattleEndPhase(this.scene, false)); this.scene.pushPhase(new NewBattlePhase(this.scene)); } else { playerPokemon.turnData.failedRunAway = true; diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 3b9ca012ef7..8926a2211e0 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -8,7 +8,7 @@ export class BattleEndPhase extends BattlePhase { /** If true, will increment battles won */ isVictory: boolean; - constructor(scene: BattleScene, isVictory: boolean = true) { + constructor(scene: BattleScene, isVictory: boolean) { super(scene); this.isVictory = isVictory; @@ -17,16 +17,17 @@ export class BattleEndPhase extends BattlePhase { start() { super.start(); + this.scene.gameData.gameStats.battles++; + if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { + this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; + } + if (this.isVictory) { this.scene.currentBattle.addBattleScore(this.scene); - this.scene.gameData.gameStats.battles++; if (this.scene.currentBattle.trainer) { this.scene.gameData.gameStats.trainersDefeated++; } - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { - this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; - } } // Endless graceful end @@ -42,7 +43,7 @@ export class BattleEndPhase extends BattlePhase { } for (const pokemon of this.scene.getPokemonAllowedInBattle()) { - applyPostBattleAbAttrs(PostBattleAbAttr, pokemon); + applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory); } if (this.scene.currentBattle.moneyScattered) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index e419aa6692d..5c33ae4b343 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -31,11 +31,8 @@ export class BerryPhase extends FieldPhase { for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { if (berryModifier.consumed) { - if (!--berryModifier.stackCount) { - this.scene.removeModifier(berryModifier); - } else { - berryModifier.consumed = false; - } + berryModifier.consumed = false; + pokemon.loseHeldItem(berryModifier); } this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used } diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index b87dff32f60..18b999ed210 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -5,7 +5,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; -import { PostSummonPhase } from "./post-summon-phase"; import { SummonMissingPhase } from "./summon-missing-phase"; import { SwitchPhase } from "./switch-phase"; import { SwitchType } from "#enums/switch-type"; @@ -26,31 +25,34 @@ export class CheckSwitchPhase extends BattlePhase { const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + // End this phase early... + + // ...if the user is playing in Set Mode if (this.scene.battleStyle === BattleStyle.SET) { - super.end(); - return; + return super.end(); } + // ...if the checked Pokemon is somehow not on the field if (this.scene.field.getAll().indexOf(pokemon) === -1) { this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); - super.end(); - return; + return super.end(); } + // ...if there are no other allowed Pokemon in the player's party to switch with if (!this.scene.getPlayerParty().slice(1).filter(p => p.isActive()).length) { - super.end(); - return; + return super.end(); } - if (pokemon.getTag(BattlerTagType.FRENZY)) { - super.end(); - return; + // ...or if any player Pokemon has an effect that prevents the checked Pokemon from switching + if (pokemon.getTag(BattlerTagType.FRENZY) + || pokemon.isTrapped() + || this.scene.getPlayerField().some(p => p.getTag(BattlerTagType.COMMANDED))) { + return super.end(); } this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.MESSAGE); - this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true)); this.end(); }, () => { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index c1d38815c2f..eab76282908 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -7,7 +7,7 @@ import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; -import { PokeballType } from "#app/enums/pokeball"; +import { PokeballType } from "#enums/pokeball"; import { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; @@ -32,6 +32,8 @@ export class CommandPhase extends FieldPhase { start() { super.start(); + this.scene.updateGameInfo(); + const commandUiHandler = this.scene.ui.handlers[Mode.COMMAND]; if (commandUiHandler) { if (this.scene.currentBattle.turn === 1 || commandUiHandler.getCursor() === Command.POKEMON) { @@ -54,6 +56,17 @@ export class CommandPhase extends FieldPhase { } } + // If the Pokemon has applied Commander's effects to its ally, skip this command + if (this.scene.currentBattle?.double && this.getPokemon().getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(this.scene) === this.getPokemon()) { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, move: { move: Moves.NONE, targets: []}, skip: true }; + } + + // Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP. + const encoreTag = this.getPokemon().getTag(BattlerTagType.ENCORE) as EncoreTag; + if (encoreTag) { + this.getPokemon().lapseTag(BattlerTagType.ENCORE); + } + if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { return this.end(); } @@ -92,7 +105,7 @@ export class CommandPhase extends FieldPhase { handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; - let success: boolean; + let success: boolean = false; switch (command) { case Command.FIGHT: @@ -232,11 +245,8 @@ export class CommandPhase extends FieldPhase { const trapTag = playerPokemon.getTag(TrappedTag); const fairyLockTag = playerPokemon.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER); - // trapTag should be defined at this point, but just in case... if (!trapTag && !fairyLockTag) { - currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; + i18next.t(`battle:noEscape${isSwitch ? "Switch" : "Flee"}`); break; } if (!isSwitch) { @@ -272,11 +282,11 @@ export class CommandPhase extends FieldPhase { break; } - if (success!) { // TODO: is the bang correct? + if (success) { this.end(); } - return success!; // TODO: is the bang correct? + return success; } cancel() { @@ -287,26 +297,6 @@ export class CommandPhase extends FieldPhase { } } - checkFightOverride(): boolean { - const pokemon = this.getPokemon(); - - const encoreTag = pokemon.getTag(EncoreTag) as EncoreTag; - - if (!encoreTag) { - return false; - } - - const moveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === encoreTag.moveId); - - if (moveIndex === -1 || !pokemon.getMoveset()[moveIndex]!.isUsable(pokemon)) { // TODO: is this bang correct? - return false; - } - - this.handleCommand(Command.FIGHT, moveIndex, false); - - return true; - } - getFieldIndex(): integer { return this.fieldIndex; } diff --git a/src/phases/damage-phase.ts b/src/phases/damage-anim-phase.ts similarity index 82% rename from src/phases/damage-phase.ts rename to src/phases/damage-anim-phase.ts index 44e3dfd4182..42f0e1ba845 100644 --- a/src/phases/damage-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,11 +1,11 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; -import { BattleSpec } from "#app/enums/battle-spec"; -import { DamageResult, HitResult } from "#app/field/pokemon"; -import * as Utils from "#app/utils"; -import { PokemonPhase } from "./pokemon-phase"; +import type BattleScene from "#app/battle-scene"; +import { type BattlerIndex } from "#app/battle"; +import { BattleSpec } from "#enums/battle-spec"; +import { type DamageResult, HitResult } from "#app/field/pokemon"; +import { fixedInt } from "#app/utils"; +import { PokemonPhase } from "#app/phases/pokemon-phase"; -export class DamagePhase extends PokemonPhase { +export class DamageAnimPhase extends PokemonPhase { private amount: integer; private damageResult: DamageResult; private critical: boolean; @@ -25,7 +25,7 @@ export class DamagePhase extends PokemonPhase { if (this.scene.moveAnimations) { this.scene.toggleInvert(true); } - this.scene.time.delayedCall(Utils.fixedInt(1000), () => { + this.scene.time.delayedCall(fixedInt(1000), () => { this.scene.toggleInvert(false); this.applyDamage(); }); diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 90aceeb46bc..803fd478fd4 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -14,6 +14,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import * as Utils from "#app/utils"; import { EggLapsePhase } from "./egg-lapse-phase"; import { EggHatchData } from "#app/data/egg-hatch-data"; +import { doShinySparkleAnim } from "#app/field/anims"; /** @@ -329,7 +330,12 @@ export class EggHatchPhase extends Phase { this.scene.validateAchv(achvs.HATCH_SHINY); } this.eggContainer.setVisible(false); - this.pokemonSprite.play(this.pokemon.getSpriteKey(true)); + const spriteKey = this.pokemon.getSpriteKey(true); + try { + this.pokemonSprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); @@ -341,8 +347,7 @@ export class EggHatchPhase extends Phase { this.pokemon.cry(); if (isShiny) { this.scene.time.delayedCall(Utils.fixedInt(500), () => { - this.pokemonShinySparkle.play(`sparkle${this.pokemon.variant ? `_${this.pokemon.variant + 1}` : ""}`); - this.scene.playSound("se/sparkle"); + doShinySparkleAnim(this.scene, this.pokemonShinySparkle, this.pokemon.variant); }); } this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => { diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 4c57be09b79..8de26ee3ef0 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -34,7 +34,7 @@ export class EggLapsePhase extends Phase { if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) { this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { // show prompt for skip, blocking inputs for 1 second - this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0); + this.scene.ui.showText(i18next.t("battle:eggSkipPrompt", { eggsToHatch: eggsToHatchCount }), 0); this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.hatchEggsSkipped(eggsToHatch); this.showSummary(); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index c4d919c0325..a4c9aa44b36 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -34,6 +34,7 @@ import { Biome } from "#enums/biome"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; +import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import i18next from "i18next"; import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; @@ -141,10 +142,6 @@ export class EncounterPhase extends BattlePhase { } else if (!(battle.waveIndex % 1000)) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); - const bossMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier && m.pokemonId === enemyPokemon.id, false) as TurnHeldItemTransferModifier; - this.scene.removeModifier(bossMBH!); - bossMBH?.setTransferrableFalse(); - this.scene.addEnemyModifier(bossMBH!); } } @@ -202,7 +199,7 @@ export class EncounterPhase extends BattlePhase { this.scene.field.add(enemyPokemon); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); const playerPokemon = this.scene.getPlayerPokemon(); - if (playerPokemon?.visible) { + if (playerPokemon?.isOnField()) { this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); } enemyPokemon.tint(0, 0.5); @@ -220,12 +217,18 @@ export class EncounterPhase extends BattlePhase { if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); this.scene.generateEnemyModifiers(); + overrideModifiers(this.scene, false); + this.scene.getEnemyField().forEach(enemy => { + overrideHeldItems(this.scene, enemy, false); + }); + } this.scene.ui.setMode(Mode.MESSAGE).then(() => { if (!this.loaded) { this.trySetWeatherIfNewBiome(); // Set weather before session gets saved - this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 10 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => { + // Game syncs to server on waves X1 and X6 (As of 1.2.0) + this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 5 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => { this.scene.disableMenu = false; if (!success) { return this.scene.reset(true); @@ -382,6 +385,9 @@ export class EncounterPhase extends BattlePhase { if (encounter.onVisualsStart) { encounter.onVisualsStart(this.scene); + } else if (encounter.spriteConfigs && introVisuals) { + // If the encounter doesn't have any special visual intro, show sparkle for shiny Pokemon + introVisuals.playShinySparkles(); } const doEncounter = () => { @@ -442,6 +448,15 @@ export class EncounterPhase extends BattlePhase { if (enemyPokemon.isShiny()) { this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); } + /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ + if (enemyPokemon.species.speciesId === Species.ETERNATUS && (this.scene.gameMode.isBattleClassicFinalBoss(this.scene.currentBattle.waveIndex) || this.scene.gameMode.isEndlessMajorBoss(this.scene.currentBattle.waveIndex))) { + const enemyMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier, false) as TurnHeldItemTransferModifier; + if (enemyMBH) { + this.scene.removeModifier(enemyMBH, true); + enemyMBH.setTransferrableFalse(); + this.scene.addEnemyModifier(enemyMBH); + } + } }); if (![ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType)) { diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 3647a237ef1..83a85009ae0 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -2,6 +2,8 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex } from "#app/battle"; import { Command } from "#app/ui/command-ui-handler"; import { FieldPhase } from "./field-phase"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; /** * Phase for determining an enemy AI's action for the next turn. @@ -34,6 +36,11 @@ export class EnemyCommandPhase extends FieldPhase { const trainer = battle.trainer; + if (battle.double && enemyPokemon.hasAbility(Abilities.COMMANDER) + && enemyPokemon.getAlly().getTag(BattlerTagType.COMMANDED)) { + this.skipTurn = true; + } + /** * If the enemy has a trainer, decide whether or not the enemy should switch * to another member in its party. diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index dec65e2c945..76e521c9b3d 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -1,17 +1,18 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { Phase } from "#app/phase"; import BattleScene, { AnySound } from "#app/battle-scene"; -import { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { FusionSpeciesFormEvolution, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; import * as Utils from "#app/utils"; import { Mode } from "#app/ui/ui"; import { cos, sin } from "#app/field/anims"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import Pokemon, { LearnMoveSituation, PlayerPokemon } from "#app/field/pokemon"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; +import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -20,6 +21,7 @@ export class EvolutionPhase extends Phase { private preEvolvedPokemonName: string; private evolution: SpeciesFormEvolution | null; + private fusionSpeciesEvolved: boolean; // Whether the evolution is of the fused species private evolutionBgm: AnySound; private evolutionHandler: EvolutionSceneHandler; @@ -39,8 +41,7 @@ export class EvolutionPhase extends Phase { this.pokemon = pokemon; this.evolution = evolution; this.lastLevel = lastLevel; - this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); - this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); + this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution; } validate(): boolean { @@ -62,9 +63,9 @@ export class EvolutionPhase extends Phase { this.scene.fadeOutBgm(undefined, false); - const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; + this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; - this.evolutionContainer = evolutionHandler.evolutionContainer; + this.evolutionContainer = this.evolutionHandler.evolutionContainer; this.evolutionBaseBg = this.scene.add.image(0, 0, "default_bg"); this.evolutionBaseBg.setOrigin(0, 0); @@ -104,7 +105,13 @@ export class EvolutionPhase extends Phase { this.scene.ui.add(this.evolutionOverlay); [ this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => { - sprite.play(this.pokemon.getSpriteKey(true)); + const spriteKey = this.pokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); @@ -117,21 +124,25 @@ export class EvolutionPhase extends Phase { sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; }); }); - + this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon); this.doEvolution(); }); } doEvolution(): void { - this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; - this.scene.ui.showText(i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { this.pokemon.cry(); this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => { [ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => { - sprite.play(evolvedPokemon.getSpriteKey(true)); + const spriteKey = evolvedPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", evolvedPokemon.shiny); @@ -145,6 +156,7 @@ export class EvolutionPhase extends Phase { }); this.scene.time.delayedCall(1000, () => { + this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); this.scene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 1, @@ -264,7 +276,8 @@ export class EvolutionPhase extends Phase { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true); + const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved ? LearnMoveSituation.EVOLUTION_FUSED : this.pokemon.fusionSpecies ? LearnMoveSituation.EVOLUTION_FUSED_BASE : LearnMoveSituation.EVOLUTION; + const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation).filter(lm => lm[0] === EVOLVE_MOVE); for (const lm of levelMoves) { this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getPlayerParty().indexOf(this.pokemon), lm[1])); } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 3e90233a38c..a0c10015fbf 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,7 +1,7 @@ import { BattlerIndex, BattleType } from "#app/battle"; import BattleScene from "#app/battle-scene"; import { applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostVictoryAbAttrs, PostFaintAbAttr, PostKnockOutAbAttr, PostVictoryAbAttr } from "#app/data/ability"; -import { BattlerTagLapseType, DestinyBondTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType, DestinyBondTag, GrudgeTag } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; @@ -12,7 +12,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; import i18next from "i18next"; -import { DamagePhase } from "./damage-phase"; +import { DamageAnimPhase } from "./damage-anim-phase"; import { GameOverPhase } from "./game-over-phase"; import { PokemonPhase } from "./pokemon-phase"; import { SwitchPhase } from "./switch-phase"; @@ -31,35 +31,45 @@ export class FaintPhase extends PokemonPhase { /** * Destiny Bond tag belonging to the currently fainting Pokemon, if applicable */ - private destinyTag?: DestinyBondTag; + private destinyTag?: DestinyBondTag | null; /** - * The source Pokemon that dealt fatal damage and should get KO'd by Destiny Bond, if applicable + * Grudge tag belonging to the currently fainting Pokemon, if applicable + */ + private grudgeTag?: GrudgeTag | null; + + /** + * The source Pokemon that dealt fatal damage */ private source?: Pokemon; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag, source?: Pokemon) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag | null, grudgeTag?: GrudgeTag | null, source?: Pokemon) { super(scene, battlerIndex); this.preventEndure = preventEndure; this.destinyTag = destinyTag; + this.grudgeTag = grudgeTag; this.source = source; } start() { super.start(); + const faintPokemon = this.getPokemon(); + if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) { this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM); } + if (!isNullOrUndefined(this.grudgeTag) && !isNullOrUndefined(this.source)) { + this.grudgeTag.lapse(faintPokemon, BattlerTagLapseType.CUSTOM, this.source); + } + if (!this.preventEndure) { - const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; + const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, faintPokemon) as PokemonInstantReviveModifier; if (instantReviveModifier) { - if (!--instantReviveModifier.stackCount) { - this.scene.removeModifier(instantReviveModifier); - } + faintPokemon.loseHeldItem(instantReviveModifier); this.scene.updateModifiers(this.player); return this.end(); } @@ -196,7 +206,7 @@ export class FaintPhase extends PokemonPhase { } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.scene.unshiftPhase(new DamageAnimPhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); this.end(); } return true; diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 410163a70e4..b042cd98294 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -39,7 +39,13 @@ export class FormChangePhase extends EvolutionPhase { this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => { [ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => { - sprite.play(transformedPokemon.getSpriteKey(true)); + const spriteKey = transformedPokemon.getSpriteKey(true); + try { + sprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } + sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", transformedPokemon.shiny); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 1302d8fc652..52d0996b946 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -23,15 +23,22 @@ import * as Utils from "#app/utils"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; +import { SessionSaveData } from "#app/system/game-data"; +import PersistentModifierData from "#app/system/modifier-data"; +import PokemonData from "#app/system/pokemon-data"; +import ChallengeData from "#app/system/challenge-data"; +import TrainerData from "#app/system/trainer-data"; +import ArenaData from "#app/system/arena-data"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; export class GameOverPhase extends BattlePhase { - private victory: boolean; + private isVictory: boolean; private firstRibbons: PokemonSpecies[] = []; - constructor(scene: BattleScene, victory?: boolean) { + constructor(scene: BattleScene, isVictory: boolean = false) { super(scene); - this.victory = !!victory; + this.isVictory = isVictory; } start() { @@ -39,22 +46,22 @@ export class GameOverPhase extends BattlePhase { // Failsafe if players somehow skip floor 200 in classic mode if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.victory = true; + this.isVictory = true; } // Handle Mystery Encounter special Game Over cases // Situations such as when player lost a battle, but it isn't treated as full Game Over - if (!this.victory && this.scene.currentBattle.mysteryEncounter?.onGameOver && !this.scene.currentBattle.mysteryEncounter.onGameOver(this.scene)) { + if (!this.isVictory && this.scene.currentBattle.mysteryEncounter?.onGameOver && !this.scene.currentBattle.mysteryEncounter.onGameOver(this.scene)) { // Do not end the game return this.end(); } // Otherwise, continue standard Game Over logic - if (this.victory && this.scene.gameMode.isEndless) { + if (this.isVictory && this.scene.gameMode.isEndless) { const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); this.scene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver()); - } else if (this.victory || !this.scene.enableRetries) { + } else if (this.isVictory || !this.scene.enableRetries) { this.handleGameOver(); } else { this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { @@ -92,7 +99,7 @@ export class GameOverPhase extends BattlePhase { this.scene.disableMenu = true; this.scene.time.delayedCall(1000, () => { let firstClear = false; - if (this.victory && newClear) { + if (this.isVictory && newClear) { if (this.scene.gameMode.isClassic) { firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); @@ -108,8 +115,8 @@ export class GameOverPhase extends BattlePhase { this.scene.gameData.gameStats.dailyRunSessionsWon++; } } - this.scene.gameData.saveRunHistory(this.scene, this.scene.gameData.getSessionSaveData(this.scene), this.victory); - const fadeDuration = this.victory ? 10000 : 5000; + + const fadeDuration = this.isVictory ? 10000 : 5000; this.scene.fadeOutBgm(fadeDuration, true); const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); activeBattlers.map(p => p.hideInfo()); @@ -119,15 +126,14 @@ export class GameOverPhase extends BattlePhase { this.scene.clearPhaseQueue(); this.scene.ui.clearText(); - if (this.victory && this.scene.gameMode.isChallenge) { + if (this.isVictory && this.scene.gameMode.isChallenge) { this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); } const clear = (endCardPhase?: EndCardPhase) => { - if (newClear) { + if (this.isVictory && newClear) { this.handleUnlocks(); - } - if (this.victory && newClear) { + for (const species of this.firstRibbons) { this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); } @@ -135,11 +141,14 @@ export class GameOverPhase extends BattlePhase { this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); } } - this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); - this.end(); + this.getRunHistoryEntry().then(runHistoryEntry => { + this.scene.gameData.saveRunHistory(this.scene, runHistoryEntry, this.isVictory); + this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); + this.end(); + }); }; - if (this.victory && this.scene.gameMode.isClassic) { + if (this.isVictory && this.scene.gameMode.isClassic) { const dialogueKey = "miscDialogue:ending"; if (!this.scene.ui.shouldSkipDialogue(dialogueKey)) { @@ -172,26 +181,23 @@ export class GameOverPhase extends BattlePhase { }); }; - /* Added a local check to see if the game is running offline on victory + /* Added a local check to see if the game is running offline If Online, execute apiFetch as intended - If Offline, execute offlineNewClear(), a localStorage implementation of newClear daily run checks */ - if (this.victory) { - if (!Utils.isLocal) { - Utils.apiFetch(`savedata/session/newclear?slot=${this.scene.sessionSlotId}&clientSessionId=${clientSessionId}`, true) - .then(response => response.json()) - .then(newClear => doGameOver(newClear)); - } else { - this.scene.gameData.offlineNewClear(this.scene).then(result => { - doGameOver(result); - }); - } + If Offline, execute offlineNewClear() only for victory, a localStorage implementation of newClear daily run checks */ + if (!Utils.isLocal || Utils.isLocalServerConnected) { + pokerogueApi.savedata.session.newclear({ slot: this.scene.sessionSlotId, isVictory: this.isVictory, clientSessionId: clientSessionId }) + .then((success) => doGameOver(!!success)); + } else if (this.isVictory) { + this.scene.gameData.offlineNewClear(this.scene).then(result => { + doGameOver(result); + }); } else { doGameOver(false); } } handleUnlocks(): void { - if (this.victory && this.scene.gameMode.isClassic) { + if (this.isVictory && this.scene.gameMode.isClassic) { if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); } @@ -215,5 +221,34 @@ export class GameOverPhase extends BattlePhase { this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); } } + + /** + * Slightly modified version of {@linkcode GameData.getSessionSaveData}. + * @returns A promise containing the {@linkcode SessionSaveData} + */ + private async getRunHistoryEntry(): Promise { + const preWaveSessionData = await this.scene.gameData.getSession(this.scene.sessionSlotId); + return { + seed: this.scene.seed, + playTime: this.scene.sessionPlayTime, + gameMode: this.scene.gameMode.modeId, + party: this.scene.getPlayerParty().map(p => new PokemonData(p)), + enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)), + modifiers: preWaveSessionData ? preWaveSessionData.modifiers : this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), + enemyModifiers: preWaveSessionData ? preWaveSessionData.enemyModifiers : this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + arena: new ArenaData(this.scene.arena), + pokeballCounts: this.scene.pokeballCounts, + money: Math.floor(this.scene.money), + score: this.scene.score, + waveIndex: this.scene.currentBattle.waveIndex, + battleType: this.scene.currentBattle.battleType, + trainer: this.scene.currentBattle.trainer ? new TrainerData(this.scene.currentBattle.trainer) : null, + gameVersion: this.scene.game.config.gameVersion, + timestamp: new Date().getTime(), + challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)), + mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1, + mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData + } as SessionSaveData; + } } diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index a2fa8a16533..4f26abc5af3 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -1,59 +1,66 @@ -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { ExpNotification } from "#app/enums/exp-notification"; -import { EvolutionPhase } from "#app/phases/evolution-phase"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; +import { EvolutionPhase } from "#app/phases/evolution-phase"; +import { LearnMovePhase } from "#app/phases/learn-move-phase"; +import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { LevelAchv } from "#app/system/achv"; +import { NumberHolder } from "#app/utils"; import i18next from "i18next"; -import * as Utils from "#app/utils"; -import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; -import { LearnMovePhase } from "./learn-move-phase"; export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { - private lastLevel: integer; - private level: integer; + protected lastLevel: number; + protected level: number; + protected pokemon: PlayerPokemon = this.getPlayerPokemon(); - constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) { + constructor(scene: BattleScene, partyMemberIndex: number, lastLevel: number, level: number) { super(scene, partyMemberIndex); this.lastLevel = lastLevel; this.level = level; - this.scene = scene; } - start() { + public override start() { super.start(); if (this.level > this.scene.gameData.gameStats.highestLevel) { this.scene.gameData.gameStats.highestLevel = this.level; } - this.scene.validateAchvs(LevelAchv, new Utils.NumberHolder(this.level)); + this.scene.validateAchvs(LevelAchv, new NumberHolder(this.level)); - const pokemon = this.getPokemon(); - const prevStats = pokemon.stats.slice(0); - pokemon.calculateStats(); - pokemon.updateInfo(); + const prevStats = this.pokemon.stats.slice(0); + this.pokemon.calculateStats(); + this.pokemon.updateInfo(); if (this.scene.expParty === ExpNotification.DEFAULT) { this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.getPokemon()), level: this.level }), null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()), null, true); + this.scene.ui.showText( + i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.pokemon), level: this.level }), + null, + () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false) + .then(() => this.end()), null, true); } else if (this.scene.expParty === ExpNotification.SKIP) { this.end(); } else { // we still want to display the stats if activated this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); } + } + + public override end() { if (this.lastLevel < 100) { // this feels like an unnecessary optimization const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); for (const lm of levelMoves) { this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); } } - if (!pokemon.pauseEvolutions) { - const evolution = pokemon.getEvolution(); + if (!this.pokemon.pauseEvolutions) { + const evolution = this.pokemon.getEvolution(); if (evolution) { - this.scene.unshiftPhase(new EvolutionPhase(this.scene, pokemon as PlayerPokemon, evolution, this.lastLevel)); + this.scene.unshiftPhase(new EvolutionPhase(this.scene, this.pokemon, evolution, this.lastLevel)); } } + return super.end(); } } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 4c813a881d3..d08fc46e563 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,11 +4,13 @@ import { AddSecondStrikeAbAttr, AlwaysHitAbAttr, applyPostAttackAbAttrs, + applyPostDamageAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, IgnoreMoveEffectsAbAttr, MaxMultiHitAbAttr, PostAttackAbAttr, + PostDamageAbAttr, PostDefendAbAttr, TypeImmunityAbAttr, } from "#app/data/ability"; @@ -25,7 +27,8 @@ import { applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, - FixedDamageAttr, + DelayedAttackAttr, + FlinchAttr, HitsTagAttr, MissEffectAttr, MoveAttr, @@ -42,7 +45,7 @@ import { VariableTargetAttr, } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import Pokemon, { HitResult, MoveResult, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { @@ -53,7 +56,7 @@ import { PokemonMultiHitModifier, } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; -import { BooleanHolder, executeIf, NumberHolder } from "#app/utils"; +import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import i18next from "i18next"; @@ -85,11 +88,30 @@ export class MoveEffectPhase extends PokemonPhase { /** All Pokemon targeted by this phase's invoked move */ const targets = this.getTargets(); - /** If the user was somehow removed from the field, end this phase */ - if (!user?.isOnField()) { + if (!user) { return super.end(); } + const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); + /** If the user was somehow removed from the field and it's not a delayed attack, end this phase */ + if (!user.isOnField()) { + if (!isDelayedAttack) { + return super.end(); + } else { + if (!user.scene) { + /** + * This happens if the Pokemon that used the delayed attack gets caught and released + * on the turn the attack would have triggered. Having access to the global scene + * in the future may solve this entirely, so for now we just cancel the hit + */ + return super.end(); + } + if (isNullOrUndefined(user.turnData)) { + user.resetTurnData(); + } + } + } + /** * Does an effect from this move override other effects on this turn? * e.g. Charging moves (Fly, etc.) on their first turn of use. @@ -107,6 +129,14 @@ export class MoveEffectPhase extends PokemonPhase { user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); + // If the user is acting again (such as due to Instruct), reset hitsLeft/hitCount so that + // the move executes correctly (ensures all hits of a multi-hit are properly calculated) + if (user.turnData.hitsLeft === 0 && user.turnData.hitCount > 0 && user.turnData.extraTurns > 0) { + user.turnData.hitsLeft = -1; + user.turnData.hitCount = 0; + user.turnData.extraTurns--; + } + /** * If this phase is for the first hit of the invoked move, * resolve the move's total hit count. This block combines the @@ -116,12 +146,10 @@ export class MoveEffectPhase extends PokemonPhase { const hitCount = new NumberHolder(1); // Assume single target for multi hit applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount); - // If Parental Bond is applicable, double the hit count - applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, targets.length, hitCount, new NumberHolder(0)); - // If Multi-Lens is applicable, multiply the hit count by 1 + the number of Multi-Lenses held by the user - if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) { - this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new NumberHolder(0)); - } + // If Parental Bond is applicable, add another hit + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); + // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses + this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value; @@ -142,9 +170,9 @@ export class MoveEffectPhase extends PokemonPhase { const hasActiveTargets = targets.some(t => t.isActive(true)); /** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */ - const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) - && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) - && !targets[0].getTag(SemiInvulnerableTag); + const isImmune = targets[0]?.hasAbilityWithAttr(TypeImmunityAbAttr) + && (targets[0]?.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) + && !targets[0]?.getTag(SemiInvulnerableTag); /** * If no targets are left for the move to hit (FAIL), or the invoked move is single-target @@ -156,7 +184,7 @@ export class MoveEffectPhase extends PokemonPhase { if (hasActiveTargets) { this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" })); moveHistoryEntry.result = MoveResult.MISS; - applyMoveAttrs(MissEffectAttr, user, null, move); + applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); } else { this.scene.queueMessage(i18next.t("battle:attackFailed")); moveHistoryEntry.result = MoveResult.FAIL; @@ -170,7 +198,7 @@ export class MoveEffectPhase extends PokemonPhase { const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex()!, playOnEmptyField).play(this.scene, move.hitsSubstitute(user, this.getFirstTarget()!), () => { + new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(this.scene, move.hitsSubstitute(user, this.getFirstTarget()!), () => { /** Has the move successfully hit a target (for damage) yet? */ let hasHit: boolean = false; for (const target of targets) { @@ -205,13 +233,18 @@ export class MoveEffectPhase extends PokemonPhase { && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)) && !target.getTag(SemiInvulnerableTag); + /** Is the target hidden by the effects of its Commander ability? */ + const isCommanding = this.scene.currentBattle.double && target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(this.scene) === target; + /** * If the move missed a target, stop all future hits against that target * and move on to the next target (if there is one). */ - if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) { + if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) { this.stopMultiHit(target); - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + if (!target.switchOutStatus) { + this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + } if (moveHistoryEntry.result === MoveResult.PENDING) { moveHistoryEntry.result = MoveResult.MISS; } @@ -280,10 +313,17 @@ export class MoveEffectPhase extends PokemonPhase { */ if (lastHit) { this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + /** + * Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr + * other damage source are calculated in damageAndUpdate in pokemon.ts + */ + if (user.turnData.hitCount > 1) { + applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user); + } } /** - * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. + * Create a Promise that applies *all* effects from the invoked move's MoveEffectAttrs. * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger * type requires different conditions to be met with respect to the move's hit result. */ @@ -496,6 +536,10 @@ export class MoveEffectPhase extends PokemonPhase { */ protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean) : () => void { return () => { + if (this.move.getMove().hasAttr(FlinchAttr)) { + return; + } + if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.getMove().hitsSubstitute(user, target)) { const flinched = new BooleanHolder(false); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 5c6c339ffa5..089386bee00 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,12 +1,34 @@ import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability"; +import { + applyAbAttrs, + applyPostMoveUsedAbAttrs, + applyPreAttackAbAttrs, + BlockRedirectAbAttr, + IncreasePpAbAttr, + PokemonTypeChangeAbAttr, + PostMoveUsedAbAttr, + RedirectMoveAbAttr, + ReduceStatusEffectDurationAbAttr +} from "#app/data/ability"; +import { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; -import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, CopyMoveAttr, frenzyMissFunc, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move"; +import { + allMoves, + applyMoveAttrs, + BypassRedirectAttr, + BypassSleepAttr, + CopyMoveAttr, + DelayedAttackAttr, + frenzyMissFunc, + HealStatusEffectAttr, + MoveFlags, + PreMoveMessageAttr +} from "#app/data/move"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { getTerrainBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon"; @@ -14,16 +36,17 @@ import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; +import { MoveChargePhase } from "#app/phases/move-charge-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { BooleanHolder, NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; -import { MoveChargePhase } from "#app/phases/move-charge-phase"; export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; @@ -98,12 +121,11 @@ export class MovePhase extends BattlePhase { // Check if move is unusable (e.g. because it's out of PP due to a mid-turn Spite). if (!this.canMove(true)) { - if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { + if (this.pokemon.isActive(true)) { this.fail(); this.showMoveText(); this.showFailedText(); } - return this.end(); } @@ -227,6 +249,32 @@ export class MovePhase extends BattlePhase { // form changes happen even before we know that the move wll execute. this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); + const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); + if (isDelayedAttack) { + // Check the player side arena if future sight is active + const futureSightTags = this.scene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); + const doomDesireTags = this.scene.arena.findTags(t => t.tagType === ArenaTagType.DOOM_DESIRE); + let fail = false; + const currentTargetIndex = targets[0].getBattlerIndex(); + for (const tag of futureSightTags) { + if ((tag as DelayedAttackTag).targetIndex === currentTargetIndex) { + fail = true; + break; + } + } + for (const tag of doomDesireTags) { + if ((tag as DelayedAttackTag).targetIndex === currentTargetIndex) { + fail = true; + break; + } + } + if (fail) { + this.showMoveText(); + this.showFailedText(); + return this.end(); + } + } + this.showMoveText(); if (moveQueue.length > 0) { @@ -329,15 +377,9 @@ export class MovePhase extends BattlePhase { } else { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); - let failedText: string | undefined; const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false)); - - if (failureMessage) { - failedText = failureMessage; - } - this.showMoveText(); - this.showFailedText(failedText); + this.showFailedText(failureMessage ?? undefined); // Remove the user from its semi-invulnerable state (if applicable) this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); @@ -487,7 +529,7 @@ export class MovePhase extends BattlePhase { * Displays the move's usage text to the player, unless it's a charge turn (ie: {@link Moves.SOLAR_BEAM Solar Beam}), * the pokemon is on a recharge turn (ie: {@link Moves.HYPER_BEAM Hyper Beam}), or a 2-turn move was interrupted (ie: {@link Moves.FLY Fly}). */ - protected showMoveText(): void { + public showMoveText(): void { if (this.move.moveId === Moves.NONE) { return; } @@ -503,7 +545,7 @@ export class MovePhase extends BattlePhase { applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents()[0], this.move.getMove()); } - protected showFailedText(failedText?: string): void { + public showFailedText(failedText?: string): void { this.scene.queueMessage(failedText ?? i18next.t("battle:attackFailed")); } } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index e7d1f7e9074..2d1c3c4ae31 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -417,6 +417,7 @@ export class MysteryEncounterBattlePhase extends Phase { } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { + scene.getPlayerField().forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); scene.pushPhase(new ReturnPhase(scene, 1)); } scene.pushPhase(new ToggleDoublePositionPhase(scene, false)); diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index 26ae11d1026..eb5431cbc56 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -1,8 +1,10 @@ import BattleScene from "#app/battle-scene"; import { SubstituteTag } from "#app/data/battler-tags"; -import { PokemonAnimType } from "#enums/pokemon-anim-type"; import Pokemon from "#app/field/pokemon"; import { BattlePhase } from "#app/phases/battle-phase"; +import { isNullOrUndefined } from "#app/utils"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { Species } from "#enums/species"; export class PokemonAnimPhase extends BattlePhase { @@ -37,14 +39,20 @@ export class PokemonAnimPhase extends BattlePhase { case PokemonAnimType.SUBSTITUTE_REMOVE: this.doSubstituteRemoveAnim(); break; + case PokemonAnimType.COMMANDER_APPLY: + this.doCommanderApplyAnim(); + break; + case PokemonAnimType.COMMANDER_REMOVE: + this.doCommanderRemoveAnim(); + break; default: this.end(); } } - doSubstituteAddAnim(): void { + private doSubstituteAddAnim(): void { const substitute = this.pokemon.getTag(SubstituteTag); - if (substitute === null) { + if (isNullOrUndefined(substitute)) { return this.end(); } @@ -106,7 +114,7 @@ export class PokemonAnimPhase extends BattlePhase { }); } - doSubstitutePreMoveAnim(): void { + private doSubstitutePreMoveAnim(): void { if (this.fieldAssets.length !== 1) { return this.end(); } @@ -135,7 +143,7 @@ export class PokemonAnimPhase extends BattlePhase { }); } - doSubstitutePostMoveAnim(): void { + private doSubstitutePostMoveAnim(): void { if (this.fieldAssets.length !== 1) { return this.end(); } @@ -164,7 +172,7 @@ export class PokemonAnimPhase extends BattlePhase { }); } - doSubstituteRemoveAnim(): void { + private doSubstituteRemoveAnim(): void { if (this.fieldAssets.length !== 1) { return this.end(); } @@ -233,4 +241,125 @@ export class PokemonAnimPhase extends BattlePhase { } }); } + + private doCommanderApplyAnim(): void { + if (!this.scene.currentBattle?.double) { + return this.end(); + } + const dondozo = this.pokemon.getAlly(); + + if (dondozo?.species?.speciesId !== Species.DONDOZO) { + return this.end(); + } + + const tatsugiriX = this.pokemon.x + this.pokemon.getSprite().x; + const tatsugiriY = this.pokemon.y + this.pokemon.getSprite().y; + + const getSourceSprite = () => { + const sprite = this.scene.addPokemonSprite(this.pokemon, tatsugiriX, tatsugiriY, this.pokemon.getSprite().texture, this.pokemon.getSprite()!.frame.name, true); + [ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]); + sprite.setPipelineData("spriteKey", this.pokemon.getBattleSpriteKey()); + sprite.setPipelineData("shiny", this.pokemon.shiny); + sprite.setPipelineData("variant", this.pokemon.variant); + sprite.setPipelineData("ignoreFieldPos", true); + sprite.setOrigin(0.5, 1); + this.pokemon.getSprite().on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame)); + this.scene.field.add(sprite); + return sprite; + }; + + const sourceSprite = getSourceSprite(); + + this.pokemon.setVisible(false); + + const sourceFpOffset = this.pokemon.getFieldPositionOffset(); + const dondozoFpOffset = dondozo.getFieldPositionOffset(); + + this.scene.playSound("se/pb_throw"); + + this.scene.tweens.add({ + targets: sourceSprite, + duration: 375, + scale: 0.5, + x: { value: tatsugiriX + (dondozoFpOffset[0] - sourceFpOffset[0]) / 2, ease: "Linear" }, + y: { value: (this.pokemon.isPlayer() ? 100 : 65) + sourceFpOffset[1], ease: "Sine.easeOut" }, + onComplete: () => { + this.scene.field.bringToTop(dondozo); + this.scene.tweens.add({ + targets: sourceSprite, + duration: 375, + scale: 0.01, + x: { value: dondozo.x, ease: "Linear" }, + y: { value: dondozo.y + dondozo.height / 2, ease: "Sine.easeIn" }, + onComplete: () => { + sourceSprite.destroy(); + this.scene.playSound("battle_anims/PRSFX- Liquidation1.wav"); + this.scene.tweens.add({ + targets: dondozo, + duration: 250, + ease: "Sine.easeInOut", + scale: 0.85, + yoyo: true, + onComplete: () => this.end() + }); + } + }); + } + }); + } + + private doCommanderRemoveAnim(): void { + // Note: unlike the other Commander animation, this is played through the + // Dondozo instead of the Tatsugiri. + const tatsugiri = this.pokemon.getAlly(); + if (isNullOrUndefined(tatsugiri)) { + console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined"); + return this.end(); + } + + const tatsuSprite = this.scene.addPokemonSprite( + tatsugiri, + this.pokemon.x + this.pokemon.getSprite().x, + this.pokemon.y + this.pokemon.getSprite().y + this.pokemon.height / 2, + tatsugiri.getSprite().texture, + tatsugiri.getSprite()!.frame.name, + true + ); + [ "spriteColors", "fusionSpriteColors" ].map(k => tatsuSprite.pipelineData[k] = tatsugiri.getSprite().pipelineData[k]); + tatsuSprite.setPipelineData("spriteKey", tatsugiri.getBattleSpriteKey()); + tatsuSprite.setPipelineData("shiny", tatsugiri.shiny); + tatsuSprite.setPipelineData("variant", tatsugiri.variant); + tatsuSprite.setPipelineData("ignoreFieldPos", true); + this.pokemon.getSprite().on("animationupdate", (_anim, frame) => tatsuSprite.setFrame(frame.textureFrame)); + + tatsuSprite.setOrigin(0.5, 1); + tatsuSprite.setScale(0.01); + + this.scene.field.add(tatsuSprite); + this.scene.field.bringToTop(this.pokemon); + tatsuSprite.setVisible(true); + + this.scene.tweens.add({ + targets: this.pokemon, + duration: 250, + ease: "Sine.easeInOut", + scale: 1.15, + yoyo: true, + onComplete: () => { + this.scene.playSound("battle_anims/PRSFX- Liquidation4.wav"); + this.scene.tweens.add({ + targets: tatsuSprite, + duration: 500, + scale: 1, + x: { value: tatsugiri.x + tatsugiri.getSprite().x, ease: "Linear" }, + y: { value: tatsugiri.y + tatsugiri.getSprite().y, ease: "Sine.easeIn" }, + onComplete: () => { + tatsugiri.setVisible(true); + tatsuSprite.destroy(); + this.end(); + } + }); + } + }); + } } diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index dc0bd235bb5..c95b92e3b64 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -21,8 +21,9 @@ export class PokemonHealPhase extends CommonAnimPhase { private revive: boolean; private healStatus: boolean; private preventFullHeal: boolean; + private fullRestorePP: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false, fullRestorePP: boolean = false) { super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); this.hpHealed = hpHealed; @@ -32,6 +33,7 @@ export class PokemonHealPhase extends CommonAnimPhase { this.revive = revive; this.healStatus = healStatus; this.preventFullHeal = preventFullHeal; + this.fullRestorePP = fullRestorePP; } start() { @@ -86,6 +88,13 @@ export class PokemonHealPhase extends CommonAnimPhase { lastStatusEffect = pokemon.status.effect; pokemon.resetStatus(); } + if (this.fullRestorePP) { + for (const move of this.getPokemon().getMoveset()) { + if (move) { + move.ppUsed = 0; + } + } + } pokemon.updateInfo().then(() => super.end()); } else if (this.healStatus && !this.revive && pokemon.status) { lastStatusEffect = pokemon.status.effect; diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 3db98d9926c..42e5b930eb1 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,6 +1,6 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex } from "#app/battle"; -import { applyPostSummonAbAttrs, PostSummonAbAttr } from "#app/data/ability"; +import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { StatusEffect } from "#app/enums/status-effect"; import { PokemonPhase } from "./pokemon-phase"; @@ -27,6 +27,12 @@ export class PostSummonPhase extends PokemonPhase { pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); } - applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); + applyPostSummonAbAttrs(PostSummonAbAttr, pokemon) + .then(() => { + const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); + field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false)); + + this.end(); + }); } } diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 08e4d7cb952..378a932cdc5 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -16,7 +16,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { start() { const pokemon = this.getPokemon(); - if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) { + if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) { pokemon.status.incrementTurn(); const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index c28cc28b592..c9e5bec845d 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -29,17 +29,26 @@ export class QuietFormChangePhase extends BattlePhase { const preName = getPokemonNameWithAffix(this.pokemon); - if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { - this.pokemon.changeForm(this.formChange).then(() => { - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); - }); + if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag) || this.pokemon.isFainted()) { + if (this.pokemon.isPlayer() || this.pokemon.isActive()) { + this.pokemon.changeForm(this.formChange).then(() => { + this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + }); + } else { + this.end(); + } return; } const getPokemonSprite = () => { const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); sprite.setOrigin(0.5, 1); - sprite.play(this.pokemon.getBattleSpriteKey()).stop(); + const spriteKey = this.pokemon.getBattleSpriteKey(); + try { + sprite.play(spriteKey).stop(); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); [ "spriteColors", "fusionSpriteColors" ].map(k => { if (this.pokemon.summonData?.speciesForm) { @@ -77,7 +86,12 @@ export class QuietFormChangePhase extends BattlePhase { this.pokemon.setVisible(false); this.pokemon.changeForm(this.formChange).then(() => { pokemonFormTintSprite.setScale(0.01); - pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop(); + const spriteKey = this.pokemon.getBattleSpriteKey(); + try { + pokemonFormTintSprite.play(spriteKey).stop(); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } pokemonFormTintSprite.setVisible(true); this.scene.tweens.add({ targets: pokemonTintSprite, diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 98975e30720..19e1ccc12ae 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -103,7 +103,7 @@ export class SelectModifierPhase extends BattlePhase { const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity, undefined, undefined, false); } else { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index ce6ebea2442..44144f9d047 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -125,10 +125,7 @@ export class StatStageChangePhase extends PokemonPhase { const whiteHerb = this.scene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as ResetNegativeStatStageModifier; // If the White Herb was applied, consume it if (whiteHerb) { - whiteHerb.stackCount--; - if (whiteHerb.stackCount <= 0) { - this.scene.removeModifier(whiteHerb); - } + pokemon.loseHeldItem(whiteHerb); this.scene.updateModifiers(this.player); } } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 119e550293c..177e09c4527 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -140,7 +140,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { this.scene.field.add(pokemon); if (!this.player) { const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; - if (playerPokemon?.visible) { + if (playerPokemon?.isOnField()) { this.scene.field.moveBelow(pokemon, playerPokemon); } this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); @@ -193,7 +193,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { this.scene.field.add(pokemon); if (!this.player) { const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; - if (playerPokemon?.visible) { + if (playerPokemon?.isOnField()) { this.scene.field.moveBelow(pokemon, playerPokemon); } this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 2abb109a529..481d64c451e 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -3,6 +3,7 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl import { Mode } from "#app/ui/ui"; import { SwitchType } from "#enums/switch-type"; import { BattlePhase } from "./battle-phase"; +import { PostSummonPhase } from "./post-summon-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; /** @@ -63,6 +64,9 @@ export class SwitchPhase extends BattlePhase { this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + // Remove any pre-existing PostSummonPhase under the same field index. + // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. + this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType; this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn)); } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 36db8b7a7e7..a1925768d83 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -1,5 +1,5 @@ import BattleScene from "#app/battle-scene"; -import { applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr } from "#app/data/ability"; +import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability"; import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; @@ -111,7 +111,7 @@ export class SwitchSummonPhase extends SummonPhase { const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false); + this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false, undefined, undefined, undefined, false); } } } @@ -138,7 +138,6 @@ export class SwitchSummonPhase extends SummonPhase { switchedInPokemon.setAlpha(0.5); } } else { - switchedInPokemon.resetBattleData(); switchedInPokemon.resetSummonData(); } this.summon(); @@ -167,10 +166,11 @@ export class SwitchSummonPhase extends SummonPhase { const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); + const lastPokemonHasForceSwitchAbAttr = this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted(); // Compensate for turn spent summoning // Or compensate for force switch move if switched out pokemon is not fainted - if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted) { + if (currentCommand === Command.POKEMON || lastPokemonIsForceSwitchedAndNotFainted || lastPokemonHasForceSwitchAbAttr) { pokemon.battleSummonData.turnCount--; pokemon.battleSummonData.waveTurnCount--; } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 8338d39b81f..88793617776 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -243,7 +243,7 @@ export class TitlePhase extends Phase { }; // If Online, calls seed fetch from db to generate daily run. If Offline, generates a daily run based on current date. - if (!Utils.isLocal) { + if (!Utils.isLocal || Utils.isLocalServerConnected) { fetchDailyRunSeed().then(seed => { if (seed) { generateDaily(seed); diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index dc1b962f47e..d797e4360ac 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -9,6 +9,8 @@ import { BattlePhase } from "./battle-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase"; import { MoneyRewardPhase } from "./money-reward-phase"; import { TrainerSlot } from "#app/data/trainer-config"; +import { Biome } from "#app/enums/biome"; +import { achvs } from "#app/system/achv"; export class TrainerVictoryPhase extends BattlePhase { constructor(scene: BattleScene) { @@ -34,11 +36,17 @@ export class TrainerVictoryPhase extends BattlePhase { } const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? + // Validate Voucher for boss trainers if (vouchers.hasOwnProperty(TrainerType[trainerType])) { if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); } } + // Breeders in Space achievement + if (this.scene.arena.biomeType === Biome.SPACE + && (trainerType === TrainerType.BREEDER || trainerType === TrainerType.EXPERT_POKEMON_BREEDER)) { + this.scene.validateAchv(achvs.BREEDERS_IN_SPACE); + } this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index 60a2e6600db..e5f1850758d 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -23,22 +23,24 @@ export class TurnEndPhase extends FieldPhase { this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); const handlePokemon = (pokemon: Pokemon) => { - pokemon.lapseTags(BattlerTagLapseType.TURN_END); + if (!pokemon.switchOutStatus) { + pokemon.lapseTags(BattlerTagLapseType.TURN_END); - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), - Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { + this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), + Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); + } + + if (!pokemon.isPlayer()) { + this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + } + + applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); } - if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); - } - - applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); - this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index dc3ee3f660a..b48b018a046 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,6 +1,6 @@ import BattleScene from "#app/battle-scene"; -import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr, ChangeMovePriorityAbAttr } from "#app/data/ability"; -import { allMoves, applyMoveAttrs, IncrementMovePriorityAttr, MoveHeaderAttr } from "#app/data/move"; +import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/ability"; +import { allMoves, MoveHeaderAttr } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import Pokemon, { PokemonMove } from "#app/field/pokemon"; @@ -98,26 +98,22 @@ export class TurnStartPhase extends FieldPhase { const aMove = allMoves[aCommand.move!.move]; const bMove = allMoves[bCommand!.move!.move]; - // The game now considers priority and applies the relevant move and ability attributes - const aPriority = new Utils.IntegerHolder(aMove.priority); - const bPriority = new Utils.IntegerHolder(bMove.priority); + const aUser = this.scene.getField(true).find(p => p.getBattlerIndex() === a)!; + const bUser = this.scene.getField(true).find(p => p.getBattlerIndex() === b)!; - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); - applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); - - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, false, aMove, aPriority); - applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, false, bMove, bPriority); + const aPriority = aMove.getPriority(aUser, false); + const bPriority = bMove.getPriority(bUser, false); // The game now checks for differences in priority levels. // If the moves share the same original priority bracket, it can check for differences in battlerBypassSpeed and return the result. // This conditional is used to ensure that Quick Claw can still activate with abilities like Stall and Mycelium Might (attack moves only) // Otherwise, the game returns the user of the move with the highest priority. - const isSameBracket = Math.ceil(aPriority.value) - Math.ceil(bPriority.value) === 0; - if (aPriority.value !== bPriority.value) { + const isSameBracket = Math.ceil(aPriority) - Math.ceil(bPriority) === 0; + if (aPriority !== bPriority) { if (isSameBracket && battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value) { return battlerBypassSpeed[a].value ? -1 : 1; } - return aPriority.value < bPriority.value ? 1 : -1; + return (aPriority < bPriority) ? 1 : -1; } } diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 1faa31655df..62479241a6c 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -41,7 +41,7 @@ export class VictoryPhase extends PokemonPhase { } if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) { - this.scene.pushPhase(new BattleEndPhase(this.scene)); + this.scene.pushPhase(new BattleEndPhase(this.scene, true)); if (this.scene.currentBattle.battleType === BattleType.TRAINER) { this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); } diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index b48ee342780..442bafa0ca7 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -51,7 +51,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { }; this.executeForAll((pokemon: Pokemon) => { - const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length; + const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length || pokemon.switchOutStatus; if (!immune) { inflictDamage(pokemon); } @@ -59,8 +59,12 @@ export class WeatherEffectPhase extends CommonAnimPhase { } } - this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct? - this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather)); + this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { + this.executeForAll((pokemon: Pokemon) => { + if (!pokemon.switchOutStatus) { + applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather); + } + }); super.start(); }); diff --git a/src/plugins/api/api-base.ts b/src/plugins/api/api-base.ts new file mode 100644 index 00000000000..5c1a30ff3ab --- /dev/null +++ b/src/plugins/api/api-base.ts @@ -0,0 +1,93 @@ +import { SESSION_ID_COOKIE_NAME } from "#app/constants"; +import { getCookie } from "#app/utils"; + +type DataType = "json" | "form-urlencoded"; + +export abstract class ApiBase { + //#region Fields + + public readonly ERR_GENERIC: string = "There was an error"; + + protected readonly base: string; + + //#region Public + + constructor(base: string) { + this.base = base; + } + + //#region Protected + + /** + * Send a GET request. + * @param path The path to send the request to. + */ + protected async doGet(path: string) { + return this.doFetch(path, { method: "GET" }); + } + + /** + * Send a POST request. + * @param path THe path to send the request to. + * @param bodyData The body-data to send. + * @param dataType The data-type of the {@linkcode bodyData}. + */ + protected async doPost(path: string, bodyData?: D, dataType: DataType = "json") { + let body: string | undefined = undefined; + const headers: HeadersInit = {}; + + if (bodyData) { + if (dataType === "json") { + body = typeof bodyData === "string" ? bodyData : JSON.stringify(bodyData); + headers["Content-Type"] = "application/json"; + } else if (dataType === "form-urlencoded") { + if (bodyData instanceof Object) { + body = this.toUrlSearchParams(bodyData).toString(); + } else { + console.warn("Could not add body data to form-urlencoded!", bodyData); + } + headers["Content-Type"] = "application/x-www-form-urlencoded"; + } else { + console.warn(`Unsupported data type: ${dataType}`); + body = String(bodyData); + headers["Content-Type"] = "text/plain"; + } + } + + return await this.doFetch(path, { method: "POST", body, headers }); + } + + /** + * A generic request helper. + * @param path The path to send the request to. + * @param config The request {@linkcode RequestInit | Configuration}. + */ + protected async doFetch(path: string, config: RequestInit): Promise { + config.headers = { + ...config.headers, + Authorization: getCookie(SESSION_ID_COOKIE_NAME), + "Content-Type": config.headers?.["Content-Type"] ?? "application/json", + }; + + if (import.meta.env.DEV) { + console.log(`Sending ${config.method ?? "GET"} request to: `, this.base + path, config); + } + + return await fetch(this.base + path, config); + } + + /** + * Helper to transform data to {@linkcode URLSearchParams} + * Any key with a value of `undefined` will be ignored. + * Any key with a value of `null` will be included. + * @param data the data to transform to {@linkcode URLSearchParams} + * @returns a {@linkcode URLSearchParams} representaton of {@linkcode data} + */ + protected toUrlSearchParams>(data: D) { + const arr = Object.entries(data) + .map(([ key, value ]) => (value !== undefined ? [ key, String(value) ] : [ key, "" ])) + .filter(([ , value ]) => value !== ""); + + return new URLSearchParams(arr); + } +} diff --git a/src/plugins/api/pokerogue-account-api.ts b/src/plugins/api/pokerogue-account-api.ts new file mode 100644 index 00000000000..66ab8d67520 --- /dev/null +++ b/src/plugins/api/pokerogue-account-api.ts @@ -0,0 +1,101 @@ +import type { + AccountInfoResponse, + AccountLoginRequest, + AccountLoginResponse, + AccountRegisterRequest, +} from "#app/@types/PokerogueAccountApi"; +import { SESSION_ID_COOKIE_NAME } from "#app/constants"; +import { ApiBase } from "#app/plugins/api/api-base"; +import { removeCookie, setCookie } from "#app/utils"; + +/** + * A wrapper for PokéRogue account API requests. + */ +export class PokerogueAccountApi extends ApiBase { + //#region Public + + /** + * Request the {@linkcode AccountInfoResponse | UserInfo} of the logged in user. + * The user is identified by the {@linkcode SESSION_ID_COOKIE_NAME | session cookie}. + */ + public async getInfo(): Promise<[data: AccountInfoResponse | null, status: number]> { + try { + const response = await this.doGet("/account/info"); + + if (response.ok) { + const resData = (await response.json()) as AccountInfoResponse; + return [ resData, response.status ]; + } else { + console.warn("Could not get account info!", response.status, response.statusText); + return [ null, response.status ]; + } + } catch (err) { + console.warn("Could not get account info!", err); + return [ null, 500 ]; + } + } + + /** + * Register a new account. + * @param registerData The {@linkcode AccountRegisterRequest} to send + * @returns An error message if something went wrong + */ + public async register(registerData: AccountRegisterRequest) { + try { + const response = await this.doPost("/account/register", registerData, "form-urlencoded"); + + if (response.ok) { + return null; + } else { + return response.text(); + } + } catch (err) { + console.warn("Register failed!", err); + } + + return "Unknown error!"; + } + + /** + * Send a login request. + * Sets the session cookie on success. + * @param loginData The {@linkcode AccountLoginRequest} to send + * @returns An error message if something went wrong + */ + public async login(loginData: AccountLoginRequest) { + try { + const response = await this.doPost("/account/login", loginData, "form-urlencoded"); + + if (response.ok) { + const loginResponse = (await response.json()) as AccountLoginResponse; + setCookie(SESSION_ID_COOKIE_NAME, loginResponse.token); + return null; + } else { + console.warn("Login failed!", response.status, response.statusText); + return response.text(); + } + } catch (err) { + console.warn("Login failed!", err); + } + + return "Unknown error!"; + } + + /** + * Send a logout request. + * **Always** (no matter if failed or not) removes the session cookie. + */ + public async logout() { + try { + const response = await this.doGet("/account/logout"); + + if (!response.ok) { + throw new Error(`${response.status}: ${response.statusText}`); + } + } catch (err) { + console.warn("Log out failed!", err); + } + + removeCookie(SESSION_ID_COOKIE_NAME); // we are always clearing the cookie. + } +} diff --git a/src/plugins/api/pokerogue-admin-api.ts b/src/plugins/api/pokerogue-admin-api.ts new file mode 100644 index 00000000000..eeba5319adc --- /dev/null +++ b/src/plugins/api/pokerogue-admin-api.ts @@ -0,0 +1,140 @@ +import type { + LinkAccountToDiscordIdRequest, + LinkAccountToGoogledIdRequest, + SearchAccountRequest, + SearchAccountResponse, + UnlinkAccountFromDiscordIdRequest, + UnlinkAccountFromGoogledIdRequest, +} from "#app/@types/PokerogueAdminApi"; +import { ApiBase } from "#app/plugins/api/api-base"; + +export class PokerogueAdminApi extends ApiBase { + public readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!"; + + /** + * Links an account to a discord id. + * @param params The {@linkcode LinkAccountToDiscordIdRequest} to send + * @returns `null` if successful, error message if not + */ + public async linkAccountToDiscord(params: LinkAccountToDiscordIdRequest) { + try { + const response = await this.doPost("/admin/account/discordLink", params, "form-urlencoded"); + + if (response.ok) { + return null; + } else { + console.warn("Could not link account with discord!", response.status, response.statusText); + + if (response.status === 404) { + return this.ERR_USERNAME_NOT_FOUND; + } + } + } catch (err) { + console.warn("Could not link account with discord!", err); + } + + return this.ERR_GENERIC; + } + + /** + * Unlinks an account from a discord id. + * @param params The {@linkcode UnlinkAccountFromDiscordIdRequest} to send + * @returns `null` if successful, error message if not + */ + public async unlinkAccountFromDiscord(params: UnlinkAccountFromDiscordIdRequest) { + try { + const response = await this.doPost("/admin/account/discordUnlink", params, "form-urlencoded"); + + if (response.ok) { + return null; + } else { + console.warn("Could not unlink account from discord!", response.status, response.statusText); + + if (response.status === 404) { + return this.ERR_USERNAME_NOT_FOUND; + } + } + } catch (err) { + console.warn("Could not unlink account from discord!", err); + } + + return this.ERR_GENERIC; + } + + /** + * Links an account to a google id. + * @param params The {@linkcode LinkAccountToGoogledIdRequest} to send + * @returns `null` if successful, error message if not + */ + public async linkAccountToGoogleId(params: LinkAccountToGoogledIdRequest) { + try { + const response = await this.doPost("/admin/account/googleLink", params, "form-urlencoded"); + + if (response.ok) { + return null; + } else { + console.warn("Could not link account with google!", response.status, response.statusText); + + if (response.status === 404) { + return this.ERR_USERNAME_NOT_FOUND; + } + } + } catch (err) { + console.warn("Could not link account with google!", err); + } + + return this.ERR_GENERIC; + } + + /** + * Unlinks an account from a google id. + * @param params The {@linkcode UnlinkAccountFromGoogledIdRequest} to send + * @returns `null` if successful, error message if not + */ + public async unlinkAccountFromGoogleId(params: UnlinkAccountFromGoogledIdRequest) { + try { + const response = await this.doPost("/admin/account/googleUnlink", params, "form-urlencoded"); + + if (response.ok) { + return null; + } else { + console.warn("Could not unlink account from google!", response.status, response.statusText); + + if (response.status === 404) { + return this.ERR_USERNAME_NOT_FOUND; + } + } + } catch (err) { + console.warn("Could not unlink account from google!", err); + } + + return this.ERR_GENERIC; + } + + /** + * Search an account. + * @param params The {@linkcode SearchAccountRequest} to send + * @returns an array of {@linkcode SearchAccountResponse} and error. Both can be `undefined` + */ + public async searchAccount(params: SearchAccountRequest): Promise<[data?: SearchAccountResponse, error?: string]> { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/admin/account/adminSearch?${urlSearchParams}`); + + if (response.ok) { + const resData: SearchAccountResponse = await response.json(); + return [ resData, undefined ]; + } else { + console.warn("Could not find account!", response.status, response.statusText); + + if (response.status === 404) { + return [ undefined, this.ERR_USERNAME_NOT_FOUND ]; + } + } + } catch (err) { + console.warn("Could not find account!", err); + } + + return [ undefined, this.ERR_GENERIC ]; + } +} diff --git a/src/plugins/api/pokerogue-api.ts b/src/plugins/api/pokerogue-api.ts new file mode 100644 index 00000000000..92d0ff1bbbb --- /dev/null +++ b/src/plugins/api/pokerogue-api.ts @@ -0,0 +1,83 @@ +import type { TitleStatsResponse } from "#app/@types/PokerogueApi"; +import { ApiBase } from "#app/plugins/api/api-base"; +import { PokerogueAccountApi } from "#app/plugins/api/pokerogue-account-api"; +import { PokerogueAdminApi } from "#app/plugins/api/pokerogue-admin-api"; +import { PokerogueDailyApi } from "#app/plugins/api/pokerogue-daily-api"; +import { PokerogueSavedataApi } from "#app/plugins/api/pokerogue-savedata-api"; + +/** + * A wrapper for PokéRogue API requests. + */ +export class PokerogueApi extends ApiBase { + //#region Fields∏ + + public readonly account: PokerogueAccountApi; + public readonly daily: PokerogueDailyApi; + public readonly admin: PokerogueAdminApi; + public readonly savedata: PokerogueSavedataApi; + + //#region Public + + constructor(base: string) { + super(base); + this.account = new PokerogueAccountApi(base); + this.daily = new PokerogueDailyApi(base); + this.admin = new PokerogueAdminApi(base); + this.savedata = new PokerogueSavedataApi(base); + } + + /** + * Request game title-stats. + */ + public async getGameTitleStats() { + try { + const response = await this.doGet("/game/titlestats"); + return (await response.json()) as TitleStatsResponse; + } catch (err) { + console.warn("Could not get game title stats!", err); + return null; + } + } + + /** + * Unlink the currently logged in user from Discord. + * @returns `true` if unlinking was successful, `false` if not + */ + public async unlinkDiscord() { + try { + const response = await this.doPost("/auth/discord/logout"); + if (response.ok) { + return true; + } else { + console.warn(`Discord unlink failed (${response.status}: ${response.statusText})`); + } + } catch (err) { + console.warn("Could not unlink Discord!", err); + } + + return false; + } + + /** + * Unlink the currently logged in user from Google. + * @returns `true` if unlinking was successful, `false` if not + */ + public async unlinkGoogle() { + try { + const response = await this.doPost("/auth/google/logout"); + if (response.ok) { + return true; + } else { + console.warn(`Google unlink failed (${response.status}: ${response.statusText})`); + } + } catch (err) { + console.warn("Could not unlink Google!", err); + } + + return false; + } + + //#endregion +} + +export const pokerogueApi = new PokerogueApi(import.meta.env.VITE_SERVER_URL ?? "http://localhost:8001"); diff --git a/src/plugins/api/pokerogue-daily-api.ts b/src/plugins/api/pokerogue-daily-api.ts new file mode 100644 index 00000000000..c9319ae7fdc --- /dev/null +++ b/src/plugins/api/pokerogue-daily-api.ts @@ -0,0 +1,57 @@ +import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#app/@types/PokerogueDailyApi"; +import { ApiBase } from "#app/plugins/api/api-base"; +import type { RankingEntry } from "#app/ui/daily-run-scoreboard"; + +/** + * A wrapper for daily-run PokéRogue API requests. + */ +export class PokerogueDailyApi extends ApiBase { + //#region Public + + /** + * Request the daily-run seed. + * @returns The active daily-run seed as `string`. + */ + public async getSeed() { + try { + const response = await this.doGet("/daily/seed"); + return response.text(); + } catch (err) { + console.warn("Could not get daily-run seed!", err); + return null; + } + } + + /** + * Get the daily rankings for a {@linkcode ScoreboardCategory}. + * @param params The {@linkcode GetDailyRankingsRequest} to send + */ + public async getRankings(params: GetDailyRankingsRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/daily/rankings?${urlSearchParams}`); + + return (await response.json()) as RankingEntry[]; + } catch (err) { + console.warn("Could not get daily rankings!", err); + return null; + } + } + + /** + * Get the page count of the daily rankings for a {@linkcode ScoreboardCategory}. + * @param params The {@linkcode GetDailyRankingsPageCountRequest} to send. + */ + public async getRankingsPageCount(params: GetDailyRankingsPageCountRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/daily/rankingpagecount?${urlSearchParams}`); + const json = await response.json(); + + return Number(json); + } catch (err) { + console.warn("Could not get daily rankings page count!", err); + return 1; + } + } +} diff --git a/src/plugins/api/pokerogue-savedata-api.ts b/src/plugins/api/pokerogue-savedata-api.ts new file mode 100644 index 00000000000..184bfbb4bdb --- /dev/null +++ b/src/plugins/api/pokerogue-savedata-api.ts @@ -0,0 +1,41 @@ +import type { UpdateAllSavedataRequest } from "#app/@types/PokerogueSavedataApi"; +import { MAX_INT_ATTR_VALUE } from "#app/constants"; +import { ApiBase } from "#app/plugins/api/api-base"; +import { PokerogueSessionSavedataApi } from "#app/plugins/api/pokerogue-session-savedata-api"; +import { PokerogueSystemSavedataApi } from "#app/plugins/api/pokerogue-system-savedata-api"; + +/** + * A wrapper for PokéRogue savedata API requests. + */ +export class PokerogueSavedataApi extends ApiBase { + //#region Fields + + public readonly system: PokerogueSystemSavedataApi; + public readonly session: PokerogueSessionSavedataApi; + + //#region Public + + constructor(base: string) { + super(base); + this.system = new PokerogueSystemSavedataApi(base); + this.session = new PokerogueSessionSavedataApi(base); + } + + /** + * Update all savedata + * @param bodyData The {@linkcode UpdateAllSavedataRequest | request data} to send + * @returns An error message if something went wrong + */ + public async updateAll(bodyData: UpdateAllSavedataRequest) { + try { + const rawBodyData = JSON.stringify(bodyData, (_k: any, v: any) => + typeof v === "bigint" ? (v <= MAX_INT_ATTR_VALUE ? Number(v) : v.toString()) : v + ); + const response = await this.doPost("/savedata/updateall", rawBodyData); + return await response.text(); + } catch (err) { + console.warn("Could not update all savedata!", err); + return "Unknown error"; + } + } +} diff --git a/src/plugins/api/pokerogue-session-savedata-api.ts b/src/plugins/api/pokerogue-session-savedata-api.ts new file mode 100644 index 00000000000..44a7b463849 --- /dev/null +++ b/src/plugins/api/pokerogue-session-savedata-api.ts @@ -0,0 +1,115 @@ +import type { + ClearSessionSavedataRequest, + ClearSessionSavedataResponse, + DeleteSessionSavedataRequest, + GetSessionSavedataRequest, + NewClearSessionSavedataRequest, + UpdateSessionSavedataRequest, +} from "#app/@types/PokerogueSessionSavedataApi"; +import { ApiBase } from "#app/plugins/api/api-base"; +import type { SessionSaveData } from "#app/system/game-data"; + +/** + * A wrapper for PokéRogue session savedata API requests. + */ +export class PokerogueSessionSavedataApi extends ApiBase { + //#region Public + + /** + * Mark a session as cleared aka "newclear".\ + * *This is **NOT** the same as {@linkcode clear | clear()}.* + * @param params The {@linkcode NewClearSessionSavedataRequest} to send + * @returns The raw savedata as `string`. + */ + public async newclear(params: NewClearSessionSavedataRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/savedata/session/newclear?${urlSearchParams}`); + const json = await response.json(); + + return Boolean(json); + } catch (err) { + console.warn("Could not newclear session!", err); + return false; + } + } + + /** + * Get a session savedata. + * @param params The {@linkcode GetSessionSavedataRequest} to send + * @returns The session as `string` + */ + public async get(params: GetSessionSavedataRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/savedata/session/get?${urlSearchParams}`); + + return await response.text(); + } catch (err) { + console.warn("Could not get session savedata!", err); + return null; + } + } + + /** + * Update a session savedata. + * @param params The {@linkcode UpdateSessionSavedataRequest} to send + * @param rawSavedata The raw savedata (as `string`) + * @returns An error message if something went wrong + */ + public async update(params: UpdateSessionSavedataRequest, rawSavedata: string) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doPost(`/savedata/session/update?${urlSearchParams}`, rawSavedata); + + return await response.text(); + } catch (err) { + console.warn("Could not update session savedata!", err); + } + + return "Unknown Error!"; + } + + /** + * Delete a session savedata slot. + * @param params The {@linkcode DeleteSessionSavedataRequest} to send + * @returns An error message if something went wrong + */ + public async delete(params: DeleteSessionSavedataRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/savedata/session/delete?${urlSearchParams}`); + + if (response.ok) { + return null; + } else { + return await response.text(); + } + } catch (err) { + console.warn("Could not delete session savedata!", err); + return "Unknown error"; + } + } + + /** + * Clears the session savedata of the given slot.\ + * *This is **NOT** the same as {@linkcode newclear | newclear()}.* + * @param params The {@linkcode ClearSessionSavedataRequest} to send + * @param sessionData The {@linkcode SessionSaveData} object + */ + public async clear(params: ClearSessionSavedataRequest, sessionData: SessionSaveData) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doPost(`/savedata/session/clear?${urlSearchParams}`, sessionData); + + return (await response.json()) as ClearSessionSavedataResponse; + } catch (err) { + console.warn("Could not clear session savedata!", err); + } + + return { + error: "Unknown error", + success: false, + } as ClearSessionSavedataResponse; + } +} diff --git a/src/plugins/api/pokerogue-system-savedata-api.ts b/src/plugins/api/pokerogue-system-savedata-api.ts new file mode 100644 index 00000000000..659584776c4 --- /dev/null +++ b/src/plugins/api/pokerogue-system-savedata-api.ts @@ -0,0 +1,77 @@ +import type { + GetSystemSavedataRequest, + UpdateSystemSavedataRequest, + VerifySystemSavedataRequest, + VerifySystemSavedataResponse, +} from "#app/@types/PokerogueSystemSavedataApi"; +import { ApiBase } from "#app/plugins/api/api-base"; + +/** + * A wrapper for PokéRogue system savedata API requests. + */ +export class PokerogueSystemSavedataApi extends ApiBase { + //#region Public + + /** + * Get a system savedata. + * @param params The {@linkcode GetSystemSavedataRequest} to send + * @returns The system savedata as `string` or `null` on error + */ + public async get(params: GetSystemSavedataRequest) { + try { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/savedata/system/get?${urlSearchParams}`); + const rawSavedata = await response.text(); + + return rawSavedata; + } catch (err) { + console.warn("Could not get system savedata!", err); + return null; + } + } + + /** + * Verify if the session is valid. + * If not the {@linkcode SystemSaveData} is returned. + * @param params The {@linkcode VerifySystemSavedataRequest} to send + * @returns A {@linkcode SystemSaveData} if **NOT** valid, otherwise `null`. + * + * TODO: add handling for errors + */ + public async verify(params: VerifySystemSavedataRequest) { + const urlSearchParams = this.toUrlSearchParams(params); + const response = await this.doGet(`/savedata/system/verify?${urlSearchParams}`); + + if (response.ok) { + const verifySavedata = (await response.json()) as VerifySystemSavedataResponse; + + if (!verifySavedata.valid) { + console.warn("Invalid system savedata!"); + return verifySavedata.systemData; + } + } else { + console.warn("System savedata verification failed!", response.status, response.statusText); + } + + return null; + } + + /** + * Update a system savedata. + * @param params The {@linkcode UpdateSystemSavedataRequest} to send + * @param rawSystemData The raw {@linkcode SystemSaveData} + * @returns An error message if something went wrong + */ + public async update(params: UpdateSystemSavedataRequest, rawSystemData: string) { + try { + const urSearchParams = this.toUrlSearchParams(params); + const response = await this.doPost(`/savedata/system/update?${urSearchParams}`, rawSystemData); + + return await response.text(); + } catch (err) { + console.warn("Could not update system savedata!", err); + } + + return "Unknown Error"; + } +} diff --git a/src/system/achv.ts b/src/system/achv.ts index d94fcba48f2..a98e396264d 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -358,7 +358,7 @@ export const achvs = { MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0), - BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 100).setSecret(), + BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; export function initAchievements() { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index e252e03afaf..3e3a6ce8f8b 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -22,7 +22,7 @@ import { vouchers, VoucherType } from "#app/system/voucher"; import { AES, enc } from "crypto-js"; import { Mode } from "#app/ui/ui"; import { clientSessionId, loggedInUser, updateUserInfo } from "#app/account"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { GameStats } from "#app/system/game-stats"; import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; @@ -33,7 +33,7 @@ import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/ import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import * as Modifier from "#app/modifier/modifier"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import ChallengeData from "#app/system/challenge-data"; import { Device } from "#enums/devices"; import { GameDataType } from "#enums/game-data-type"; @@ -48,7 +48,7 @@ import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { ArenaTrapTag } from "#app/data/arena-tag"; export const defaultStarterSpecies: Species[] = [ @@ -397,8 +397,7 @@ export class GameData { localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(systemData, bypassLogin)); if (!bypassLogin) { - Utils.apiPost(`savedata/system/update?clientSessionId=${clientSessionId}`, systemData, undefined, true) - .then(response => response.text()) + pokerogueApi.savedata.system.update({ clientSessionId }, systemData) .then(error => { this.scene.ui.savingIcon.hide(); if (error) { @@ -428,23 +427,22 @@ export class GameData { } if (!bypassLogin) { - Utils.apiFetch(`savedata/system/get?clientSessionId=${clientSessionId}`, true) - .then(response => response.text()) - .then(response => { - if (!response.length || response[0] !== "{") { - if (response.startsWith("sql: no rows in result set")) { + pokerogueApi.savedata.system.get({ clientSessionId }) + .then(saveDataOrErr => { + if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") { + if (saveDataOrErr?.startsWith("sql: no rows in result set")) { this.scene.queueMessage("Save data could not be found. If this is a new account, you can safely ignore this message.", null, true); return resolve(true); - } else if (response.indexOf("Too many connections") > -1) { + } else if (saveDataOrErr?.includes("Too many connections")) { this.scene.queueMessage("Too many people are trying to connect and the server is overloaded. Please try again later.", null, true); return resolve(false); } - console.error(response); + console.error(saveDataOrErr); return resolve(false); } const cachedSystem = localStorage.getItem(`data_${loggedInUser?.username}`); - this.initSystem(response, cachedSystem ? AES.decrypt(cachedSystem, saveKey).toString(enc.Utf8) : undefined).then(resolve); + this.initSystem(saveDataOrErr, cachedSystem ? AES.decrypt(cachedSystem, saveKey).toString(enc.Utf8) : undefined).then(resolve); }); } else { this.initSystem(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin)).then(resolve); // TODO: is this bang correct? @@ -508,9 +506,9 @@ export class GameData { const starterIds = Object.keys(this.starterData).map(s => parseInt(s) as Species); for (const s of starterIds) { - this.starterData[s].candyCount += this.dexData[s].caughtCount; - this.starterData[s].candyCount += this.dexData[s].hatchedCount * 2; - if (this.dexData[s].caughtAttr & DexAttr.SHINY) { + this.starterData[s].candyCount += systemData.dexData[s].caughtCount; + this.starterData[s].candyCount += systemData.dexData[s].hatchedCount * 2; + if (systemData.dexData[s].caughtAttr & DexAttr.SHINY) { this.starterData[s].candyCount += 4; } } @@ -580,6 +578,7 @@ export class GameData { if (!Utils.isLocal) { /** * Networking Code DO NOT DELETE! + * Note: Might have to be migrated to `pokerogue-api.ts` * const response = await Utils.apiFetch("savedata/runHistory", true); const data = await response.json(); @@ -660,6 +659,7 @@ export class GameData { return false; } } + NOTE: should be adopted to `pokerogue-api.ts` */ return true; } @@ -679,7 +679,7 @@ export class GameData { return ret; } - return k.endsWith("Attr") && ![ "natureAttr", "abilityAttr", "passiveAttr" ].includes(k) ? BigInt(v) : v; + return k.endsWith("Attr") && ![ "natureAttr", "abilityAttr", "passiveAttr" ].includes(k) ? BigInt(v ?? 0) : v; }) as SystemSaveData; } @@ -704,12 +704,11 @@ export class GameData { return true; } - const response = await Utils.apiFetch(`savedata/system/verify?clientSessionId=${clientSessionId}`, true) - .then(response => response.json()); + const systemData = await pokerogueApi.savedata.system.verify({ clientSessionId }); - if (!response.valid) { + if (systemData) { this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene, JSON.stringify(response.systemData))); + this.scene.unshiftPhase(new ReloadSessionPhase(this.scene, JSON.stringify(systemData))); this.clearLocalData(); return false; } @@ -984,10 +983,9 @@ export class GameData { }; if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`)) { - Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true) - .then(response => response.text()) + pokerogueApi.savedata.session.get({ slot: slotId, clientSessionId }) .then(async response => { - if (!response.length || response[0] !== "{") { + if (!response || response?.length === 0 || response?.[0] !== "{") { console.error(response); return resolve(null); } @@ -1149,14 +1147,7 @@ export class GameData { if (success !== null && !success) { return resolve(false); } - Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => { - if (response.ok) { - loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); - resolve(true); - } - return response.text(); - }).then(error => { + pokerogueApi.savedata.session.delete({ slot: slotId, clientSessionId }).then(error => { if (error) { if (error.startsWith("session out of date")) { this.scene.clearPhaseQueue(); @@ -1164,8 +1155,15 @@ export class GameData { } console.error(error); resolve(false); + } else { + if (loggedInUser) { + loggedInUser.lastSessionSlot = -1; + } + + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); + resolve(true); + } - resolve(true); }); }); }); @@ -1215,17 +1213,15 @@ export class GameData { result = [ true, true ]; } else { const sessionData = this.getSessionSaveData(scene); - const response = await Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true); + const { trainerId } = this; + const jsonResponse = await pokerogueApi.savedata.session.clear({ slot: slotId, trainerId, clientSessionId }, sessionData); - if (response.ok) { - loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); - } - - const jsonResponse: PokerogueApiClearSessionData = await response.json(); - - if (!jsonResponse.error) { - result = [ true, jsonResponse.success ?? false ]; + if (!jsonResponse?.error) { + result = [ true, jsonResponse?.success ?? false ]; + if (loggedInUser) { + loggedInUser!.lastSessionSlot = -1; + } + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); } else { if (jsonResponse && jsonResponse.error?.startsWith("session out of date")) { this.scene.clearPhaseQueue(); @@ -1342,8 +1338,7 @@ export class GameData { console.debug("Session data saved"); if (!bypassLogin && sync) { - Utils.apiPost("savedata/updateall", JSON.stringify(request, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), undefined, true) - .then(response => response.text()) + pokerogueApi.savedata.updateAll(request) .then(error => { if (sync) { this.scene.lastSavePlayTime = 0; @@ -1387,18 +1382,24 @@ export class GameData { link.remove(); }; if (!bypassLogin && dataType < GameDataType.SETTINGS) { - Utils.apiFetch(`savedata/${dataType === GameDataType.SYSTEM ? "system" : "session"}/get?clientSessionId=${clientSessionId}${dataType === GameDataType.SESSION ? `&slot=${slotId}` : ""}`, true) - .then(response => response.text()) - .then(response => { - if (!response.length || response[0] !== "{") { - console.error(response); - resolve(false); - return; - } + let promise: Promise = Promise.resolve(null); - handleData(response); - resolve(true); - }); + if (dataType === GameDataType.SYSTEM) { + promise = pokerogueApi.savedata.system.get({ clientSessionId }); + } else if (dataType === GameDataType.SESSION) { + promise = pokerogueApi.savedata.session.get({ slot: slotId, clientSessionId }); + } + + promise.then(response => { + if (!response?.length || response[0] !== "{") { + console.error(response); + resolve(false); + return; + } + + handleData(response); + resolve(true); + }); } else { const data = localStorage.getItem(dataKey); if (data) { @@ -1477,14 +1478,14 @@ export class GameData { if (!success[0]) { return displayError(`Could not contact the server. Your ${dataName} data could not be imported.`); } - let url: string; + const { trainerId, secretId } = this; + let updatePromise: Promise; if (dataType === GameDataType.SESSION) { - url = `savedata/session/update?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`; + updatePromise = pokerogueApi.savedata.session.update({ slot: slotId, trainerId, secretId, clientSessionId }, dataStr); } else { - url = `savedata/system/update?trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`; + updatePromise = pokerogueApi.savedata.system.update({ trainerId, secretId, clientSessionId }, dataStr); } - Utils.apiPost(url, dataStr, undefined, true) - .then(response => response.text()) + updatePromise .then(error => { if (error) { console.error(error); @@ -1539,7 +1540,7 @@ export class GameData { entry.caughtAttr = defaultStarterAttr; entry.natureAttr = 1 << (defaultStarterNatures[ds] + 1); for (const i in entry.ivs) { - entry.ivs[i] = 10; + entry.ivs[i] = 15; } } @@ -1790,6 +1791,32 @@ export class GameData { }); } + /** + * Checks whether the root species of a given {@PokemonSpecies} has been unlocked in the dex + */ + isRootSpeciesUnlocked(species: PokemonSpecies): boolean { + return !!this.dexData[species.getRootSpeciesId()]?.caughtAttr; + } + + /** + * Unlocks the given {@linkcode Nature} for a {@linkcode PokemonSpecies} and its prevolutions. + * Will fail silently if root species has not been unlocked + */ + unlockSpeciesNature(species: PokemonSpecies, nature: Nature): void { + if (!this.isRootSpeciesUnlocked(species)) { + return; + } + + //recursively unlock nature for species and prevolutions + const _unlockSpeciesNature = (speciesId: Species) => { + this.dexData[speciesId].natureAttr |= 1 << (nature + 1); + if (pokemonPrevolutions.hasOwnProperty(speciesId)) { + _unlockSpeciesNature(pokemonPrevolutions[speciesId]); + } + }; + _unlockSpeciesNature(species.speciesId); + } + updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void { let dexEntry: DexEntry; do { diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 485c1d90c1d..64801cc0ff1 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -1,8 +1,8 @@ import { BattleType } from "../battle"; import BattleScene from "../battle-scene"; import { Gender } from "../data/gender"; -import { Nature } from "../data/nature"; -import { PokeballType } from "../data/pokeball"; +import { Nature } from "#enums/nature"; +import { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; @@ -171,7 +171,7 @@ export default class PokemonData { playerPokemon.nickname = this.nickname; } }) - : scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, this); + : scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this); if (this.summonData) { ret.primeSummonData(this.summonData); } diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index d71edf603dd..64ddfdae5cf 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -1,9 +1,9 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import BattleScene from "../../battle-scene"; -import { hasTouchscreen } from "../../touch-controls"; -import { updateWindowType } from "../../ui/ui-theme"; -import { CandyUpgradeNotificationChangedEvent } from "../../events/battle-scene"; +import BattleScene from "#app/battle-scene"; +import { hasTouchscreen } from "#app/touch-controls"; +import { updateWindowType } from "#app/ui/ui-theme"; +import { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; import { EaseType } from "#enums/ease-type"; import { MoneyFormat } from "#enums/money-format"; @@ -44,6 +44,7 @@ const OFF_ON: SettingOption[] = [ label: i18next.t("settings:on") } ]; + const AUTO_DISABLED: SettingOption[] = [ { value: "Auto", @@ -55,6 +56,19 @@ const AUTO_DISABLED: SettingOption[] = [ } ]; +const TOUCH_CONTROLS_OPTIONS: SettingOption[] = [ + { + value: "Auto", + label: i18next.t("settings:auto") + }, + { + value: "Disabled", + label: i18next.t("settings:disabled"), + needConfirmation: true, + confirmationMessage: i18next.t("settings:confirmDisableTouch") + } +]; + const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [ { value: "Rewards", @@ -100,7 +114,9 @@ export enum SettingType { type SettingOption = { value: string, - label: string + label: string, + needConfirmation?: boolean, + confirmationMessage?: string }; export interface Setting { @@ -344,13 +360,6 @@ export const Setting: Array = [ default: 1, type: SettingType.GENERAL }, - { - key: SettingKeys.Touch_Controls, - label: i18next.t("settings:touchControls"), - options: AUTO_DISABLED, - default: 0, - type: SettingType.GENERAL - }, { key: SettingKeys.Vibration, label: i18next.t("settings:vibrations"), @@ -358,6 +367,28 @@ export const Setting: Array = [ default: 0, type: SettingType.GENERAL }, + { + key: SettingKeys.Touch_Controls, + label: i18next.t("settings:touchControls"), + options: TOUCH_CONTROLS_OPTIONS, + default: 0, + type: SettingType.GENERAL, + isHidden: () => !hasTouchscreen() + }, + { + key: SettingKeys.Move_Touch_Controls, + label: i18next.t("settings:moveTouchControls"), + options: [ + { + value: "Configure", + label: i18next.t("settings:change") + } + ], + default: 0, + type: SettingType.GENERAL, + activatable: true, + isHidden: () => !hasTouchscreen() + }, { key: SettingKeys.Language, label: i18next.t("settings:language"), @@ -643,20 +674,6 @@ export const Setting: Array = [ type: SettingType.AUDIO, requireReload: true }, - { - key: SettingKeys.Move_Touch_Controls, - label: i18next.t("settings:moveTouchControls"), - options: [ - { - value: "Configure", - label: i18next.t("settings:change") - } - ], - default: 0, - type: SettingType.GENERAL, - activatable: true, - isHidden: () => !hasTouchscreen() - }, { key: SettingKeys.Shop_Cursor_Target, label: i18next.t("settings:shopCursorTarget"), @@ -849,7 +866,7 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): if (scene.ui) { const cancelHandler = () => { scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true); }; const changeLocaleHandler = (locale: string): boolean => { try { diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index e96afb5cbd4..d0c69dc3213 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -129,7 +129,7 @@ class SessionVersionConverter extends VersionConverter { if (curMajor === 1) { if (curMinor === 0) { - if (curPatch <= 4) { + if (curPatch <= 5) { console.log("Applying v1.0.4 session data migration!"); this.callMigrators(data, v1_0_4.sessionMigrators); } diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index f16b6bcb6bb..95f0337ecdd 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -1,6 +1,8 @@ -import { SettingKeys } from "../../settings/settings"; -import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data"; -import { allSpecies } from "../../../data/pokemon-species"; +import { SettingKeys } from "#app/system/settings/settings"; +import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "#app/system/game-data"; +import { allSpecies } from "#app/data/pokemon-species"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { isNullOrUndefined } from "#app/utils"; export const systemMigrators = [ /** @@ -46,12 +48,14 @@ export const systemMigrators = [ * @param data {@linkcode SystemSaveData} */ function fixStarterData(data: SystemSaveData) { - for (const starterId of defaultStarterSpecies) { - if (data.starterData[starterId]?.abilityAttr) { - data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; - } - if (data.dexData[starterId]?.caughtAttr) { - data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; + if (!isNullOrUndefined(data.starterData)) { + for (const starterId of defaultStarterSpecies) { + if (data.starterData[starterId]?.abilityAttr) { + data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; + } + if (data.dexData[starterId]?.caughtAttr) { + data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; + } } } } @@ -131,5 +135,28 @@ export const sessionMigrators = [ m.className = "ResetNegativeStatStageModifier"; } }); + }, + /** + * Converts old Pokemon natureOverride and mysteryEncounterData + * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. + * @param data {@linkcode SessionSaveData} + */ + function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { + // Fix Pokemon nature overrides and custom data migration + data.party.forEach(pokemon => { + if (pokemon["mysteryEncounterPokemonData"]) { + pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]); + pokemon["mysteryEncounterPokemonData"] = null; + } + if (pokemon["fusionMysteryEncounterPokemonData"]) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]); + pokemon["fusionMysteryEncounterPokemonData"] = null; + } + pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); + if (!isNullOrUndefined(pokemon["natureOverride"]) && pokemon["natureOverride"] >= 0) { + pokemon.customPokemonData.nature = pokemon["natureOverride"]; + pokemon["natureOverride"] = -1; + } + }); } ] as const; diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts index aac554c4531..5d6247aeaa2 100644 --- a/src/system/version_migration/versions/v1_1_0.ts +++ b/src/system/version_migration/versions/v1_1_0.ts @@ -1,32 +1,5 @@ -import { SessionSaveData } from "../../game-data"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; - export const systemMigrators = [] as const; export const settingsMigrators = [] as const; -export const sessionMigrators = [ - /** - * Converts old Pokemon natureOverride and mysteryEncounterData - * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. - * @param data {@linkcode SessionSaveData} - */ - function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { - // Fix Pokemon nature overrides and custom data migration - data.party.forEach(pokemon => { - if (pokemon["mysteryEncounterPokemonData"]) { - pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]); - pokemon["mysteryEncounterPokemonData"] = null; - } - if (pokemon["fusionMysteryEncounterPokemonData"]) { - pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]); - pokemon["fusionMysteryEncounterPokemonData"] = null; - } - pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); - if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) { - pokemon.customPokemonData.nature = pokemon["natureOverride"]; - pokemon["natureOverride"] = -1; - } - }); - } -] as const; +export const sessionMigrators = [] as const; diff --git a/src/test/abilities/analytic.test.ts b/src/test/abilities/analytic.test.ts new file mode 100644 index 00000000000..12777c545f0 --- /dev/null +++ b/src/test/abilities/analytic.test.ts @@ -0,0 +1,81 @@ +import { BattlerIndex } from "#app/battle"; +import { isBetween, toDmgValue } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Analytic", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.TACKLE ]) + .ability(Abilities.ANALYTIC) + .battleType("single") + .disableCrits() + .startingLevel(200) + .enemyLevel(200) + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should increase damage if the user moves last", async () => { + await game.classicMode.startBattle([ Species.ARCEUS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + const damage1 = enemy.getInverseHp(); + enemy.hp = enemy.getMaxHp(); + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(isBetween(enemy.getInverseHp(), toDmgValue(damage1 * 1.3) - 3, toDmgValue(damage1 * 1.3) + 3)).toBe(true); + }); + + it("should increase damage only if the user moves last in doubles", async () => { + game.override.battleType("double"); + await game.classicMode.startBattle([ Species.GENGAR, Species.SHUCKLE ]); + + const [ enemy, ] = game.scene.getEnemyField(); + + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.toNextTurn(); + const damage1 = enemy.getInverseHp(); + enemy.hp = enemy.getMaxHp(); + + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + expect(isBetween(enemy.getInverseHp(), toDmgValue(damage1 * 1.3) - 3, toDmgValue(damage1 * 1.3) + 3)).toBe(true); + enemy.hp = enemy.getMaxHp(); + + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(enemy.getInverseHp()).toBe(damage1); + }); +}); diff --git a/src/test/abilities/arena_trap.test.ts b/src/test/abilities/arena_trap.test.ts index 5068fed6b77..12b9673080d 100644 --- a/src/test/abilities/arena_trap.test.ts +++ b/src/test/abilities/arena_trap.test.ts @@ -1,9 +1,10 @@ +import { allAbilities } from "#app/data/ability"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; describe("Abilities - Arena Trap", () => { let phaserGame: Phaser.Game; @@ -55,4 +56,39 @@ describe("Abilities - Arena Trap", () => { expect(game.scene.getEnemyField().length).toBe(2); }); + + /** + * This checks if the Player Pokemon is able to switch out/run away after the Enemy Pokemon with {@linkcode Abilities.ARENA_TRAP} + * is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR} + * + * Note: It should be able to switch out/run away + */ + it("should lift if pokemon with this ability leaves the field", async () => { + game.override + .battleType("double") + .enemyMoveset(Moves.SPLASH) + .moveset([ Moves.ROAR, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.SUDOWOODO, Species.LUNATONE ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + const [ player1, player2 ] = game.scene.getPlayerField(); + + vi.spyOn(enemy1, "getAbility").mockReturnValue(allAbilities[Abilities.ARENA_TRAP]); + + game.move.select(Moves.ROAR); + game.move.select(Moves.SPLASH, 1); + + // This runs the fist command phase where the moves are selected + await game.toNextTurn(); + // During the next command phase the player pokemons should not be trapped anymore + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(player1.isTrapped()).toBe(false); + expect(player2.isTrapped()).toBe(false); + expect(enemy1.isOnField()).toBe(false); + expect(enemy2.isOnField()).toBe(true); + }); }); diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index e9970e1c049..db7ed01e7d9 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,8 +1,9 @@ import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move"; -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/src/test/abilities/commander.test.ts b/src/test/abilities/commander.test.ts new file mode 100644 index 00000000000..99b3d50797c --- /dev/null +++ b/src/test/abilities/commander.test.ts @@ -0,0 +1,224 @@ +import { BattlerIndex } from "#app/battle"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { EffectiveStat, Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Commander", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .startingLevel(100) + .enemyLevel(100) + .moveset([ Moves.LIQUIDATION, Moves.MEMENTO, Moves.SPLASH, Moves.FLIP_TURN ]) + .ability(Abilities.COMMANDER) + .battleType("double") + .disableCrits() + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.TACKLE); + + vi.spyOn(game.scene, "triggerPokemonBattleAnim").mockReturnValue(true); + }); + + it("causes the source to jump into Dondozo's mouth, granting a stat boost and hiding the source", async () => { + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + const affectedStats: EffectiveStat[] = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + affectedStats.forEach((stat) => expect(dondozo.getStatStage(stat)).toBe(2)); + + game.move.select(Moves.SPLASH, 1); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + // Force both enemies to target the Tatsugiri + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + + await game.phaseInterceptor.to("BerryPhase", false); + game.scene.getEnemyField().forEach(enemy => expect(enemy.getLastXMoves(1)[0].result).toBe(MoveResult.MISS)); + expect(tatsugiri.isFullHp()).toBeTruthy(); + }); + + it("should activate when a Dondozo switches in and cancel the source's move", async () => { + game.override.enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.MAGIKARP, Species.DONDOZO ]); + + const tatsugiri = game.scene.getPlayerField()[0]; + + game.move.select(Moves.LIQUIDATION, 0, BattlerIndex.ENEMY); + game.doSwitchPokemon(2); + + await game.phaseInterceptor.to("MovePhase", false); + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + + const dondozo = game.scene.getPlayerField()[1]; + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(tatsugiri.getMoveHistory()).toHaveLength(0); + expect(game.scene.getEnemyField()[0].isFullHp()).toBeTruthy(); + }); + + it("source should reenter the field when Dondozo faints", async () => { + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.MEMENTO, 1, BattlerIndex.ENEMY); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER); + + await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER ]); + + await game.phaseInterceptor.to("FaintPhase", false); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeUndefined(); + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(dondozo, PokemonAnimType.COMMANDER_REMOVE); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(tatsugiri.isFullHp()).toBeFalsy(); + }); + + it("source should still take damage from Poison while hidden", async () => { + game.override + .statusEffect(StatusEffect.POISON) + .enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.SPLASH, 1); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(tatsugiri.isFullHp()).toBeFalsy(); + }); + + it("source should still take damage from Salt Cure while hidden", async () => { + game.override.enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + tatsugiri.addTag(BattlerTagType.SALT_CURED, 0, Moves.NONE, game.scene.getField()[BattlerIndex.ENEMY].id); + + game.move.select(Moves.SPLASH, 1); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(tatsugiri.isFullHp()).toBeFalsy(); + }); + + it("source should still take damage from Sandstorm while hidden", async () => { + game.override + .weather(WeatherType.SANDSTORM) + .enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.SPLASH, 1); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.phaseInterceptor.to("TurnEndPhase"); + expect(tatsugiri.isFullHp()).toBeFalsy(); + }); + + it("should make Dondozo immune to being forced out", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.WHIRLWIND ]); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.SPLASH, 1); + + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.forceEnemyMove(Moves.WHIRLWIND, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + + // Test may time out here if Whirlwind forced out a Pokemon + await game.phaseInterceptor.to("TurnEndPhase"); + expect(dondozo.isActive(true)).toBeTruthy(); + }); + + it("should interrupt the source's semi-invulnerability", async () => { + game.override + .moveset([ Moves.SPLASH, Moves.DIVE ]) + .enemyMoveset(Moves.SPLASH); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.MAGIKARP, Species.DONDOZO ]); + + const tatsugiri = game.scene.getPlayerField()[0]; + + game.move.select(Moves.DIVE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("CommandPhase"); + await game.toNextTurn(); + + expect(tatsugiri.getTag(BattlerTagType.UNDERWATER)).toBeDefined(); + game.doSwitchPokemon(2); + + await game.phaseInterceptor.to("MovePhase", false); + const dondozo = game.scene.getPlayerField()[1]; + expect(tatsugiri.getTag(BattlerTagType.UNDERWATER)).toBeUndefined(); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + await game.toNextTurn(); + const enemy = game.scene.getEnemyField()[0]; + expect(enemy.isFullHp()).toBeTruthy(); + }); +}); diff --git a/src/test/abilities/corrosion.test.ts b/src/test/abilities/corrosion.test.ts new file mode 100644 index 00000000000..e607e85defb --- /dev/null +++ b/src/test/abilities/corrosion.test.ts @@ -0,0 +1,46 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Corrosion", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .battleType("single") + .disableCrits() + .enemySpecies(Species.GRIMER) + .enemyAbility(Abilities.CORROSION) + .enemyMoveset(Moves.TOXIC); + }); + + it("If a Poison- or Steel-type Pokémon with this Ability poisons a target with Synchronize, Synchronize does not gain the ability to poison Poison- or Steel-type Pokémon.", async () => { + game.override.ability(Abilities.SYNCHRONIZE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const playerPokemon = game.scene.getPlayerPokemon(); + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(playerPokemon!.status).toBeUndefined(); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + expect(playerPokemon!.status).toBeDefined(); + expect(enemyPokemon!.status).toBeUndefined(); + }); +}); diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index f44782207cb..07a84bd7a5a 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -1,10 +1,10 @@ import { BattlerIndex } from "#app/battle"; -import { StatusEffect } from "#app/data/status-effect"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/abilities/flash_fire.test.ts b/src/test/abilities/flash_fire.test.ts index f03e1689649..0ca55227ea4 100644 --- a/src/test/abilities/flash_fire.test.ts +++ b/src/test/abilities/flash_fire.test.ts @@ -1,11 +1,11 @@ import { BattlerIndex } from "#app/battle"; -import { StatusEffect } from "#app/data/status-effect"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Species } from "#app/enums/species"; import { MovePhase } from "#app/phases/move-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/abilities/forecast.test.ts b/src/test/abilities/forecast.test.ts index 18d43a67a9d..6d1f776da16 100644 --- a/src/test/abilities/forecast.test.ts +++ b/src/test/abilities/forecast.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { Abilities } from "#app/enums/abilities"; import { WeatherType } from "#app/enums/weather-type"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PostSummonPhase } from "#app/phases/post-summon-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; @@ -273,7 +273,7 @@ describe("Abilities - Forecast", () => { const castform = game.scene.getPlayerPokemon()!; // Damage phase should come first - await game.phaseInterceptor.to(DamagePhase); + await game.phaseInterceptor.to(DamageAnimPhase); expect(castform.hp).toBeLessThan(castform.getMaxHp()); // Then change form diff --git a/src/test/abilities/galvanize.test.ts b/src/test/abilities/galvanize.test.ts index 438f166174d..80e767866ea 100644 --- a/src/test/abilities/galvanize.test.ts +++ b/src/test/abilities/galvanize.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/src/test/abilities/honey_gather.test.ts b/src/test/abilities/honey_gather.test.ts new file mode 100644 index 00000000000..fc9d6cdd150 --- /dev/null +++ b/src/test/abilities/honey_gather.test.ts @@ -0,0 +1,74 @@ +import type { CommandPhase } from "#app/phases/command-phase"; +import { Command } from "#app/ui/command-ui-handler"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Abilities - Honey Gather", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.ROAR, Moves.THUNDERBOLT ]) + .startingLevel(100) + .ability(Abilities.HONEY_GATHER) + .passiveAbility(Abilities.RUN_AWAY) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should give money when winning a battle", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + game.scene.money = 1000; + + game.move.select(Moves.THUNDERBOLT); + await game.toNextWave(); + + expect(game.scene.money).toBeGreaterThan(1000); + }); + + it("should not give money when the enemy pokemon flees", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + game.scene.money = 1000; + + game.move.select(Moves.ROAR); + await game.toNextTurn(); + + expect(game.scene.money).toBe(1000); + expect(game.scene.currentBattle.waveIndex).toBe(2); + }); + + it("should not give money when the player flees", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + game.scene.money = 1000; + + // something weird is going on with the test framework, so this is required to prevent a crash + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene); + + const commandPhase = game.scene.getCurrentPhase() as CommandPhase; + commandPhase.handleCommand(Command.RUN, 0); + await game.toNextTurn(); + + expect(game.scene.money).toBe(1000); + expect(game.scene.currentBattle.waveIndex).toBe(2); + }); +}); diff --git a/src/test/abilities/hustle.test.ts b/src/test/abilities/hustle.test.ts index c4c4818040d..08a441315fb 100644 --- a/src/test/abilities/hustle.test.ts +++ b/src/test/abilities/hustle.test.ts @@ -42,7 +42,7 @@ describe("Abilities - Hustle", () => { game.move.select(Moves.TACKLE); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(pikachu.getEffectiveStat).toHaveReturnedWith(Math.floor(atk * 1.5)); }); @@ -68,7 +68,7 @@ describe("Abilities - Hustle", () => { vi.spyOn(pikachu, "getAccuracyMultiplier"); game.move.select(Moves.GIGA_DRAIN); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk); expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1); @@ -86,7 +86,7 @@ describe("Abilities - Hustle", () => { vi.spyOn(allMoves[Moves.FISSURE], "calculateBattleAccuracy"); game.move.select(Moves.FISSURE); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(enemyPokemon.turnData.damageTaken).toBe(enemyPokemon.getMaxHp()); expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1); diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index aac1cb16d97..e7bc9eeeb63 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -1,6 +1,6 @@ import { allMoves } from "#app/data/move"; -import { Type } from "#app/data/type"; -import { Weather, WeatherType } from "#app/data/weather"; +import { Type } from "#enums/type"; +import { Weather } from "#app/data/weather"; import { PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; @@ -8,6 +8,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; diff --git a/src/test/abilities/magic_guard.test.ts b/src/test/abilities/magic_guard.test.ts index 8075eac66f2..7c038354748 100644 --- a/src/test/abilities/magic_guard.test.ts +++ b/src/test/abilities/magic_guard.test.ts @@ -1,12 +1,13 @@ import { ArenaTagSide, getArenaTag } from "#app/data/arena-tag"; -import { StatusEffect, getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; -import { WeatherType } from "#app/data/weather"; +import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/abilities/mimicry.test.ts b/src/test/abilities/mimicry.test.ts index 8f22de33061..29aa1d649d3 100644 --- a/src/test/abilities/mimicry.test.ts +++ b/src/test/abilities/mimicry.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/abilities/parental_bond.test.ts b/src/test/abilities/parental_bond.test.ts index 993a5eb8344..c2f54fa4cfc 100644 --- a/src/test/abilities/parental_bond.test.ts +++ b/src/test/abilities/parental_bond.test.ts @@ -1,14 +1,14 @@ -import { Stat } from "#enums/stat"; -import { StatusEffect } from "#app/data/status-effect"; -import { Type } from "#app/data/type"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { Type } from "#enums/type"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - Parental Bond", () => { @@ -51,7 +51,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.TACKLE); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp; enemyStartingHp = enemyPokemon.hp; @@ -129,7 +129,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.SELF_DESTRUCT); - await game.phaseInterceptor.to("DamagePhase", false); + await game.phaseInterceptor.to("DamageAnimPhase", false); expect(leadPokemon.turnData.hitCount).toBe(1); } @@ -147,7 +147,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.ROLLOUT); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase", false); + await game.phaseInterceptor.to("DamageAnimPhase", false); expect(leadPokemon.turnData.hitCount).toBe(1); } @@ -181,7 +181,7 @@ describe("Abilities - Parental Bond", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; game.move.select(Moves.COUNTER); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); const playerDamage = leadPokemon.getMaxHp() - leadPokemon.hp; @@ -221,7 +221,7 @@ describe("Abilities - Parental Bond", () => { const leadPokemon = game.scene.getPlayerPokemon()!; game.move.select(Moves.EARTHQUAKE); - await game.phaseInterceptor.to("DamagePhase", false); + await game.phaseInterceptor.to("DamageAnimPhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); } @@ -238,7 +238,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.MIND_BLOWN); - await game.phaseInterceptor.to("DamagePhase", false); + await game.phaseInterceptor.to("DamageAnimPhase", false); expect(leadPokemon.turnData.hitCount).toBe(2); @@ -274,7 +274,7 @@ describe("Abilities - Parental Bond", () => { ); it( - "Moves boosted by this ability and Multi-Lens should strike 4 times", + "Moves boosted by this ability and Multi-Lens should strike 3 times", async () => { game.override.moveset([ Moves.TACKLE ]); game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); @@ -285,38 +285,14 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.TACKLE); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); - expect(leadPokemon.turnData.hitCount).toBe(4); + expect(leadPokemon.turnData.hitCount).toBe(3); } ); it( - "Super Fang boosted by this ability and Multi-Lens should strike twice", - async () => { - game.override.moveset([ Moves.SUPER_FANG ]); - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); - - await game.classicMode.startBattle([ Species.MAGIKARP ]); - - const leadPokemon = game.scene.getPlayerPokemon()!; - const enemyPokemon = game.scene.getEnemyPokemon()!; - - game.move.select(Moves.SUPER_FANG); - await game.move.forceHit(); - - await game.phaseInterceptor.to("DamagePhase"); - - expect(leadPokemon.turnData.hitCount).toBe(2); - - await game.phaseInterceptor.to("MoveEndPhase", false); - - expect(enemyPokemon.hp).toBe(Math.ceil(enemyPokemon.getMaxHp() * 0.25)); - } - ); - - it( - "Seismic Toss boosted by this ability and Multi-Lens should strike twice", + "Seismic Toss boosted by this ability and Multi-Lens should strike 3 times", async () => { game.override.moveset([ Moves.SEISMIC_TOSS ]); game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); @@ -331,9 +307,9 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.SEISMIC_TOSS); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); - expect(leadPokemon.turnData.hitCount).toBe(2); + expect(leadPokemon.turnData.hitCount).toBe(3); await game.phaseInterceptor.to("MoveEndPhase", false); @@ -353,7 +329,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.HYPER_BEAM); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(leadPokemon.turnData.hitCount).toBe(2); expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined(); @@ -377,7 +353,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.ANCHOR_SHOT); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(leadPokemon.turnData.hitCount).toBe(2); expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); @@ -404,7 +380,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.SMACK_DOWN); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(leadPokemon.turnData.hitCount).toBe(2); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined(); @@ -448,7 +424,7 @@ describe("Abilities - Parental Bond", () => { game.move.select(Moves.WAKE_UP_SLAP); await game.move.forceHit(); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(leadPokemon.turnData.hitCount).toBe(2); expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP); @@ -495,29 +471,24 @@ describe("Abilities - Parental Bond", () => { } ); - it( - "should not apply to multi-target moves with Multi-Lens", - async () => { - game.override.battleType("double"); - game.override.moveset([ Moves.EARTHQUAKE, Moves.SPLASH ]); - game.override.passiveAbility(Abilities.LEVITATE); - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); + it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => { + game.override.enemyLevel(1000) + .moveset(Moves.FUTURE_SIGHT); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); - await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + const enemyPokemon = game.scene.getEnemyPokemon()!; + vi.spyOn(enemyPokemon, "damageAndUpdate"); - const enemyPokemon = game.scene.getEnemyField(); + game.move.select(Moves.FUTURE_SIGHT); + await game.toNextTurn(); - const enemyStartingHp = enemyPokemon.map(p => p.hp); + game.doSwitchPokemon(1); + await game.toNextTurn(); - game.move.select(Moves.EARTHQUAKE); - game.move.select(Moves.SPLASH, 1); + game.doSwitchPokemon(2); + await game.toNextTurn(); - await game.phaseInterceptor.to("DamagePhase"); - const enemyFirstHitDamage = enemyStartingHp.map((hp, i) => hp - enemyPokemon[i].hp); - - await game.phaseInterceptor.to("BerryPhase", false); - - enemyPokemon.forEach((p, i) => expect(enemyStartingHp[i] - p.hp).toBe(2 * enemyFirstHitDamage[i])); - } - ); + // TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field + expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); + }); }); diff --git a/src/test/abilities/pastel_veil.test.ts b/src/test/abilities/pastel_veil.test.ts index 6f09fd8ff06..dd8360493a1 100644 --- a/src/test/abilities/pastel_veil.test.ts +++ b/src/test/abilities/pastel_veil.test.ts @@ -1,10 +1,10 @@ import { BattlerIndex } from "#app/battle"; -import { StatusEffect } from "#app/data/status-effect"; import { Abilities } from "#app/enums/abilities"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index bb80e9f3ac8..aaab5ddb5c4 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -1,9 +1,10 @@ -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 5f86ec4c6e6..0d7202e3f6d 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -1,6 +1,6 @@ import { allMoves } from "#app/data/move"; -import { Type } from "#app/data/type"; -import { Weather, WeatherType } from "#app/data/weather"; +import { Type } from "#enums/type"; +import { Weather } from "#app/data/weather"; import { PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; @@ -8,6 +8,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; diff --git a/src/test/abilities/sand_veil.test.ts b/src/test/abilities/sand_veil.test.ts index c62357f17af..ee8ca450df9 100644 --- a/src/test/abilities/sand_veil.test.ts +++ b/src/test/abilities/sand_veil.test.ts @@ -1,12 +1,12 @@ import { StatMultiplierAbAttr, allAbilities } from "#app/data/ability"; -import { Stat } from "#enums/stat"; -import { WeatherType } from "#app/data/weather"; import { CommandPhase } from "#app/phases/command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; diff --git a/src/test/abilities/sap_sipper.test.ts b/src/test/abilities/sap_sipper.test.ts index a4ce0c1b8f6..dc254a54b54 100644 --- a/src/test/abilities/sap_sipper.test.ts +++ b/src/test/abilities/sap_sipper.test.ts @@ -8,7 +8,8 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; // See also: TypeImmunityAbAttr describe("Abilities - Sap Sipper", () => { @@ -27,20 +28,20 @@ describe("Abilities - Sap Sipper", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("single"); - game.override.disableCrits(); + game.override.battleType("single") + .disableCrits() + .ability(Abilities.SAP_SIPPER) + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.SAP_SIPPER) + .enemyMoveset(Moves.SPLASH); }); it("raises ATK stat stage by 1 and block effects when activated against a grass attack", async() => { const moveToUse = Moves.LEAFAGE; - const enemyAbility = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.DUSKULL); - game.override.enemyAbility(enemyAbility); + game.override.moveset(moveToUse); - await game.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const enemyPokemon = game.scene.getEnemyPokemon()!; const initialEnemyHp = enemyPokemon.hp; @@ -55,14 +56,10 @@ describe("Abilities - Sap Sipper", () => { it("raises ATK stat stage by 1 and block effects when activated against a grass status move", async() => { const moveToUse = Moves.SPORE; - const enemyAbility = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(enemyAbility); + game.override.moveset(moveToUse); - await game.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -76,14 +73,10 @@ describe("Abilities - Sap Sipper", () => { it("do not activate against status moves that target the field", async () => { const moveToUse = Moves.GRASSY_TERRAIN; - const enemyAbility = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(enemyAbility); + game.override.moveset(moveToUse); - await game.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); game.move.select(moveToUse); @@ -96,14 +89,10 @@ describe("Abilities - Sap Sipper", () => { it("activate once against multi-hit grass attacks", async () => { const moveToUse = Moves.BULLET_SEED; - const enemyAbility = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(enemyAbility); + game.override.moveset(moveToUse); - await game.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const enemyPokemon = game.scene.getEnemyPokemon()!; const initialEnemyHp = enemyPokemon.hp; @@ -118,15 +107,10 @@ describe("Abilities - Sap Sipper", () => { it("do not activate against status moves that target the user", async () => { const moveToUse = Moves.SPIKY_SHIELD; - const ability = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.ability(ability); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(Abilities.NONE); + game.override.moveset(moveToUse); - await game.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const playerPokemon = game.scene.getPlayerPokemon()!; @@ -142,18 +126,15 @@ describe("Abilities - Sap Sipper", () => { expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase"); }); - // TODO Add METRONOME outcome override - // To run this testcase, manually modify the METRONOME move to always give SAP_SIPPER, then uncomment - it.todo("activate once against multi-hit grass attacks (metronome)", async () => { + it("activate once against multi-hit grass attacks (metronome)", async () => { const moveToUse = Moves.METRONOME; - const enemyAbility = Abilities.SAP_SIPPER; - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE ]); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(enemyAbility); + const randomMoveAttr = allMoves[Moves.METRONOME].findAttr(attr => attr instanceof RandomMoveAttr) as RandomMoveAttr; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.BULLET_SEED); - await game.startBattle(); + game.override.moveset(moveToUse); + + await game.classicMode.startBattle([ Species.BULBASAUR ]); const enemyPokemon = game.scene.getEnemyPokemon()!; const initialEnemyHp = enemyPokemon.hp; @@ -168,11 +149,8 @@ describe("Abilities - Sap Sipper", () => { it("still activates regardless of accuracy check", async () => { game.override.moveset(Moves.LEAF_BLADE); - game.override.enemyMoveset(Moves.SPLASH); - game.override.enemySpecies(Species.MAGIKARP); - game.override.enemyAbility(Abilities.SAP_SIPPER); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const enemyPokemon = game.scene.getEnemyPokemon()!; diff --git a/src/test/abilities/schooling.test.ts b/src/test/abilities/schooling.test.ts index 5f953dbf9ab..e1ec58f517e 100644 --- a/src/test/abilities/schooling.test.ts +++ b/src/test/abilities/schooling.test.ts @@ -1,9 +1,10 @@ -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts index 3318c7fc27a..6f9b2195a9c 100644 --- a/src/test/abilities/serene_grace.test.ts +++ b/src/test/abilities/serene_grace.test.ts @@ -1,15 +1,12 @@ import { BattlerIndex } from "#app/battle"; -import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; -import { Stat } from "#enums/stat"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import * as Utils from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; - +import { allMoves } from "#app/data/move"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { FlinchAttr } from "#app/data/move"; describe("Abilities - Serene Grace", () => { let phaserGame: Phaser.Game; @@ -27,66 +24,29 @@ describe("Abilities - Serene Grace", () => { beforeEach(() => { game = new GameManager(phaserGame); - const movesToUse = [ Moves.AIR_SLASH, Moves.TACKLE ]; - game.override.battleType("single"); - game.override.enemySpecies(Species.ONIX); - game.override.startingLevel(100); - game.override.moveset(movesToUse); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override + .disableCrits() + .battleType("single") + .ability(Abilities.SERENE_GRACE) + .moveset([ Moves.AIR_SLASH ]) + .enemySpecies(Species.ALOLA_GEODUDE) + .enemyLevel(10) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH ]); }); - it("Move chance without Serene Grace", async () => { - const moveToUse = Moves.AIR_SLASH; - await game.startBattle([ - Species.PIDGEOT - ]); + it("Serene Grace should double the secondary effect chance of a move", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + const airSlashMove = allMoves[Moves.AIR_SLASH]; + const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0]; + vi.spyOn(airSlashFlinchAttr, "getMoveChance"); - game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000; - expect(game.scene.getPlayerParty()[0].formIndex).toBe(0); - - game.move.select(moveToUse); - + game.move.select(Moves.AIR_SLASH); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); + await game.move.forceHit(); + await game.phaseInterceptor.to("BerryPhase"); - // Check chance of Air Slash without Serene Grace - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.AIR_SLASH); - - const chance = new Utils.IntegerHolder(move.chance); - console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - expect(chance.value).toBe(30); - - }, 20000); - - it("Move chance with Serene Grace", async () => { - const moveToUse = Moves.AIR_SLASH; - game.override.ability(Abilities.SERENE_GRACE); - await game.startBattle([ - Species.TOGEKISS - ]); - - game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000; - expect(game.scene.getPlayerParty()[0].formIndex).toBe(0); - - game.move.select(moveToUse); - - await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); - - // Check chance of Air Slash with Serene Grace - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.AIR_SLASH); - - const chance = new Utils.IntegerHolder(move.chance); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - expect(chance.value).toBe(60); - - }, 20000); - - //TODO King's Rock Interaction Unit Test + expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(60); + }); }); diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index 826694752b7..a0ddf5bb9c6 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -1,15 +1,13 @@ import { BattlerIndex } from "#app/battle"; -import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; -import { NumberHolder } from "#app/utils"; +import { Type } from "#app/enums/type"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { allMoves } from "#app/data/move"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { allMoves, FlinchAttr } from "#app/data/move"; describe("Abilities - Sheer Force", () => { let phaserGame: Phaser.Game; @@ -27,143 +25,91 @@ describe("Abilities - Sheer Force", () => { beforeEach(() => { game = new GameManager(phaserGame); - const movesToUse = [ Moves.AIR_SLASH, Moves.BIND, Moves.CRUSH_CLAW, Moves.TACKLE ]; - game.override.battleType("single"); - game.override.enemySpecies(Species.ONIX); - game.override.startingLevel(100); - game.override.moveset(movesToUse); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override + .battleType("single") + .ability(Abilities.SHEER_FORCE) + .enemySpecies(Species.ONIX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH ]) + .disableCrits(); }); - it("Sheer Force", async () => { - const moveToUse = Moves.AIR_SLASH; - game.override.ability(Abilities.SHEER_FORCE); + const SHEER_FORCE_MULT = 5461 / 4096; + + it("Sheer Force should boost the power of the move but disable secondary effects", async () => { + game.override.moveset([ Moves.AIR_SLASH ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const airSlashMove = allMoves[Moves.AIR_SLASH]; + vi.spyOn(airSlashMove, "calculateBattlePower"); + const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0]; + vi.spyOn(airSlashFlinchAttr, "getMoveChance"); + + game.move.select(Moves.AIR_SLASH); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(airSlashMove.calculateBattlePower).toHaveLastReturnedWith(airSlashMove.power * SHEER_FORCE_MULT); + expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(0); + }); + + it("Sheer Force does not affect the base damage or secondary effects of binding moves", async () => { + game.override.moveset([ Moves.BIND ]); + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const bindMove = allMoves[Moves.BIND]; + vi.spyOn(bindMove, "calculateBattlePower"); + + game.move.select(Moves.BIND); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power); + }, 20000); + + it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => { + game.override.moveset([ Moves.TACKLE ]); await game.classicMode.startBattle([ Species.PIDGEOT ]); - game.scene.getEnemyPokemon()!.stats[Stat.SPDEF] = 10000; - expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0); - - game.move.select(moveToUse); + const tackleMove = allMoves[Moves.TACKLE]; + vi.spyOn(tackleMove, "calculateBattlePower"); + game.move.select(Moves.TACKLE); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); + await game.move.forceHit(); + await game.phaseInterceptor.to("BerryPhase", false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.AIR_SLASH); + expect(tackleMove.calculateBattlePower).toHaveLastReturnedWith(tackleMove.power); + }); - //Verify the move is boosted and has no chance of secondary effects - const power = new NumberHolder(move.power); - const chance = new NumberHolder(move.chance); + it("Sheer Force can disable the on-hit activation of specific abilities", async () => { + game.override + .moveset([ Moves.HEADBUTT ]) + .enemySpecies(Species.SQUIRTLE) + .enemyLevel(10) + .enemyAbility(Abilities.COLOR_CHANGE); - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power); - - expect(chance.value).toBe(0); - expect(power.value).toBe(move.power * 5461 / 4096); - - - }, 20000); - - it("Sheer Force with exceptions including binding moves", async () => { - const moveToUse = Moves.BIND; - game.override.ability(Abilities.SHEER_FORCE); await game.classicMode.startBattle([ Species.PIDGEOT ]); + const enemyPokemon = game.scene.getEnemyPokemon(); + const headbuttMove = allMoves[Moves.HEADBUTT]; + vi.spyOn(headbuttMove, "calculateBattlePower"); + const headbuttFlinchAttr = headbuttMove.getAttrs(FlinchAttr)[0]; + vi.spyOn(headbuttFlinchAttr, "getMoveChance"); - - game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000; - expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0); - - game.move.select(moveToUse); + game.move.select(Moves.HEADBUTT); await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); + await game.move.forceHit(); + await game.phaseInterceptor.to("BerryPhase", false); - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.BIND); - - //Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1 - const power = new NumberHolder(move.power); - const chance = new NumberHolder(move.chance); - - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power); - - expect(chance.value).toBe(-1); - expect(power.value).toBe(move.power); - - - }, 20000); - - it("Sheer Force with moves with no secondary effect", async () => { - const moveToUse = Moves.TACKLE; - game.override.ability(Abilities.SHEER_FORCE); - await game.classicMode.startBattle([ Species.PIDGEOT ]); - - - game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000; - expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0); - - game.move.select(moveToUse); - - await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); - - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.TACKLE); - - //Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1 - const power = new NumberHolder(move.power); - const chance = new NumberHolder(move.chance); - - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power); - - expect(chance.value).toBe(-1); - expect(power.value).toBe(move.power); - - - }, 20000); - - it("Sheer Force Disabling Specific Abilities", async () => { - const moveToUse = Moves.CRUSH_CLAW; - game.override.enemyAbility(Abilities.COLOR_CHANGE); - game.override.startingHeldItems([{ name: "KINGS_ROCK", count: 1 }]); - game.override.ability(Abilities.SHEER_FORCE); - await game.startBattle([ Species.PIDGEOT ]); - - - game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000; - expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0); - - game.move.select(moveToUse); - - await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); - await game.phaseInterceptor.to(MoveEffectPhase, false); - - const phase = game.scene.getCurrentPhase() as MoveEffectPhase; - const move = phase.move.getMove(); - expect(move.id).toBe(Moves.CRUSH_CLAW); - - //Disable color change due to being hit by Sheer Force - const power = new NumberHolder(move.power); - const chance = new NumberHolder(move.chance); - const user = phase.getUserPokemon()!; - const target = phase.getFirstTarget()!; - const opponentType = target.getTypes()[0]; - - applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false); - applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power); - applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move)); - - expect(chance.value).toBe(0); - expect(power.value).toBe(move.power * 5461 / 4096); - expect(target.getTypes().length).toBe(2); - expect(target.getTypes()[0]).toBe(opponentType); - - }, 20000); + expect(enemyPokemon?.getTypes()[0]).toBe(Type.WATER); + expect(headbuttMove.calculateBattlePower).toHaveLastReturnedWith(headbuttMove.power * SHEER_FORCE_MULT); + expect(headbuttFlinchAttr.getMoveChance).toHaveLastReturnedWith(0); + }); it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => { const moveToUse = Moves.CRUNCH; @@ -191,5 +137,19 @@ describe("Abilities - Sheer Force", () => { expect(onix.getTypes()).toStrictEqual(expectedTypes); }); - //TODO King's Rock Interaction Unit Test + it("Sheer Force should disable Meloetta's transformation from Relic Song", async () => { + game.override + .ability(Abilities.SHEER_FORCE) + .moveset([ Moves.RELIC_SONG ]) + .enemyMoveset([ Moves.SPLASH ]) + .enemyLevel(100); + await game.classicMode.startBattle([ Species.MELOETTA ]); + + const playerPokemon = game.scene.getPlayerPokemon(); + const formKeyStart = playerPokemon?.getFormKey(); + + game.move.select(Moves.RELIC_SONG); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(formKeyStart).toBe(playerPokemon?.getFormKey()); + }); }); diff --git a/src/test/abilities/shields_down.test.ts b/src/test/abilities/shields_down.test.ts index fbb2e96e463..6100d3e04d9 100644 --- a/src/test/abilities/shields_down.test.ts +++ b/src/test/abilities/shields_down.test.ts @@ -1,9 +1,10 @@ -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; diff --git a/src/test/abilities/speed_boost.test.ts b/src/test/abilities/speed_boost.test.ts index dd2e83aaa88..ff5184eedae 100644 --- a/src/test/abilities/speed_boost.test.ts +++ b/src/test/abilities/speed_boost.test.ts @@ -28,7 +28,9 @@ describe("Abilities - Speed Boost", () => { game.override .battleType("single") - .enemySpecies(Species.DRAGAPULT) + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyLevel(100) .ability(Abilities.SPEED_BOOST) .enemyMoveset(Moves.SPLASH) .moveset([ Moves.SPLASH, Moves.U_TURN ]); @@ -70,21 +72,23 @@ describe("Abilities - Speed Boost", () => { Species.NINJASK ]); - game.move.select(Moves.U_TURN); - game.doSelectPartyPokemon(1); - await game.toNextTurn(); - let playerPokemon = game.scene.getPlayerPokemon()!; - expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0); + const [ shuckle, ninjask ] = game.scene.getPlayerParty(); game.move.select(Moves.U_TURN); game.doSelectPartyPokemon(1); await game.toNextTurn(); - playerPokemon = game.scene.getPlayerPokemon()!; - expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0); + expect(game.scene.getPlayerPokemon()!).toBe(ninjask); + expect(ninjask.getStatStage(Stat.SPD)).toBe(0); + + game.move.select(Moves.U_TURN); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!).toBe(shuckle); + expect(shuckle.getStatStage(Stat.SPD)).toBe(0); game.move.select(Moves.SPLASH); await game.toNextTurn(); - expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1); + expect(shuckle.getStatStage(Stat.SPD)).toBe(1); }); it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after", diff --git a/src/test/abilities/stakeout.test.ts b/src/test/abilities/stakeout.test.ts new file mode 100644 index 00000000000..885169b284e --- /dev/null +++ b/src/test/abilities/stakeout.test.ts @@ -0,0 +1,85 @@ +import { BattlerIndex } from "#app/battle"; +import { isBetween } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Stakeout", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.SURF ]) + .ability(Abilities.STAKEOUT) + .battleType("single") + .disableCrits() + .startingLevel(100) + .enemyLevel(100) + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH, Moves.FLIP_TURN ]) + .startingWave(5); + }); + + it("should do double damage to a pokemon that switched out", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + + const [ enemy1, ] = game.scene.getEnemyParty(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const damage1 = enemy1.getInverseHp(); + enemy1.hp = enemy1.getMaxHp(); + + game.move.select(Moves.SPLASH); + game.forceEnemyToSwitch(); + await game.toNextTurn(); + + game.move.select(Moves.SURF); + game.forceEnemyToSwitch(); + await game.toNextTurn(); + + expect(enemy1.isFainted()).toBe(false); + expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true); + }); + + it("should do double damage to a pokemon that switched out via U-Turn/etc", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + + const [ enemy1, ] = game.scene.getEnemyParty(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + const damage1 = enemy1.getInverseHp(); + enemy1.hp = enemy1.getMaxHp(); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.FLIP_TURN); + await game.toNextTurn(); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.FLIP_TURN); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(enemy1.isFainted()).toBe(false); + expect(isBetween(enemy1.getInverseHp(), (damage1 * 2) - 5, (damage1 * 2) + 5)).toBe(true); + }); +}); diff --git a/src/test/abilities/sturdy.test.ts b/src/test/abilities/sturdy.test.ts index 49384e69f83..07ccbbb68e5 100644 --- a/src/test/abilities/sturdy.test.ts +++ b/src/test/abilities/sturdy.test.ts @@ -1,5 +1,5 @@ import { EnemyPokemon } from "#app/field/pokemon"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -55,7 +55,7 @@ describe("Abilities - Sturdy", () => { enemyPokemon.hp = enemyPokemon.getMaxHp() - 1; game.move.select(Moves.CLOSE_COMBAT); - await game.phaseInterceptor.to(DamagePhase); + await game.phaseInterceptor.to(DamageAnimPhase); expect(enemyPokemon.hp).toBe(0); expect(enemyPokemon.isFainted()).toBe(true); @@ -81,7 +81,7 @@ describe("Abilities - Sturdy", () => { await game.startBattle(); game.move.select(Moves.CLOSE_COMBAT); - await game.phaseInterceptor.to(DamagePhase); + await game.phaseInterceptor.to(DamageAnimPhase); const enemyPokemon: EnemyPokemon = game.scene.getEnemyParty()[0]; expect(enemyPokemon.hp).toBe(0); diff --git a/src/test/abilities/synchronize.test.ts b/src/test/abilities/synchronize.test.ts index d34b5631271..2ae80ae9c7a 100644 --- a/src/test/abilities/synchronize.test.ts +++ b/src/test/abilities/synchronize.test.ts @@ -1,7 +1,7 @@ -import { StatusEffect } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -94,16 +94,4 @@ describe("Abilities - Synchronize", () => { expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS); expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase"); }); - - it("should activate with Psycho Shift after the move clears the status", async () => { - game.override.statusEffect(StatusEffect.PARALYSIS); - await game.classicMode.startBattle(); - - game.move.select(Moves.PSYCHO_SHIFT); - await game.phaseInterceptor.to("BerryPhase"); - - expect(game.scene.getPlayerPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS); // keeping old gen < V impl for now since it's buggy otherwise - expect(game.scene.getEnemyPokemon()!.status?.effect).toBe(StatusEffect.PARALYSIS); - expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase"); - }); }); diff --git a/src/test/abilities/unburden.test.ts b/src/test/abilities/unburden.test.ts index 7cd69f4a075..ba14c7fdcd0 100644 --- a/src/test/abilities/unburden.test.ts +++ b/src/test/abilities/unburden.test.ts @@ -1,18 +1,35 @@ +import { BattlerIndex } from "#app/battle"; +import { PostItemLostAbAttr } from "#app/data/ability"; +import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; +import Pokemon from "#app/field/pokemon"; +import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { Stat } from "#enums/stat"; -import { BerryType } from "#app/enums/berry-type"; -import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; describe("Abilities - Unburden", () => { let phaserGame: Phaser.Game; let game: GameManager; + /** + * Count the number of held items a Pokemon has, accounting for stacks of multiple items. + */ + function getHeldItemCount(pokemon: Pokemon): number { + const stackCounts = pokemon.getHeldItems().map(m => m.getStackCount()); + if (stackCounts.length) { + return stackCounts.reduce((a, b) => a + b); + } else { + return 0; + } + } + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -27,9 +44,9 @@ describe("Abilities - Unburden", () => { game = new GameManager(phaserGame); game.override .battleType("single") - .starterSpecies(Species.TREECKO) .startingLevel(1) - .moveset([ Moves.POPULATION_BOMB, Moves.KNOCK_OFF, Moves.PLUCK, Moves.THIEF ]) + .ability(Abilities.UNBURDEN) + .moveset([ Moves.SPLASH, Moves.KNOCK_OFF, Moves.PLUCK, Moves.FALSE_SWIPE ]) .startingHeldItems([ { name: "BERRY", count: 1, type: BerryType.SITRUS }, { name: "BERRY", count: 2, type: BerryType.APICOT }, @@ -37,209 +54,348 @@ describe("Abilities - Unburden", () => { ]) .enemySpecies(Species.NINJASK) .enemyLevel(100) - .enemyMoveset([ Moves.FALSE_SWIPE ]) + .enemyMoveset(Moves.SPLASH) .enemyAbility(Abilities.UNBURDEN) .enemyPassiveAbility(Abilities.NO_GUARD) .enemyHeldItems([ { name: "BERRY", type: BerryType.SITRUS, count: 1 }, { name: "BERRY", type: BerryType.LUM, count: 1 }, ]); + // For the various tests that use Thief, give it a 100% steal rate + vi.spyOn(allMoves[Moves.THIEF], "attrs", "get").mockReturnValue([ new StealHeldItemChanceAttr(1.0) ]); }); it("should activate when a berry is eaten", async () => { - await game.classicMode.startBattle(); + game.override.enemyMoveset(Moves.FALSE_SWIPE); + await game.classicMode.startBattle([ Species.TREECKO ]); const playerPokemon = game.scene.getPlayerPokemon()!; - playerPokemon.abilityIndex = 2; - const playerHeldItems = playerPokemon.getHeldItems().length; + const playerHeldItems = getHeldItemCount(playerPokemon); const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); - game.move.select(Moves.FALSE_SWIPE); + // Player gets hit by False Swipe and eats its own Sitrus Berry + game.move.select(Moves.SPLASH); await game.toNextTurn(); - expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems); - expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2); + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); }); - it("should activate when a berry is stolen", async () => { - await game.classicMode.startBattle(); + it("should activate when a berry is eaten, even if Berry Pouch preserves the berry", async () => { + game.override.enemyMoveset(Moves.FALSE_SWIPE) + .startingModifier([{ name: "BERRY_POUCH", count: 5850 }]); + await game.classicMode.startBattle([ Species.TREECKO ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerHeldItems = getHeldItemCount(playerPokemon); + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + + // Player gets hit by False Swipe and eats its own Sitrus Berry + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(getHeldItemCount(playerPokemon)).toBe(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); + }); + + it("should activate for the target, and not the stealer, when a berry is stolen", async () => { + await game.classicMode.startBattle([ Species.TREECKO ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; + const enemyHeldItemCt = getHeldItemCount(enemyPokemon); const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); + // Player uses Pluck and eats the opponent's berry game.move.select(Moves.PLUCK); await game.toNextTurn(); - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); + expect(getHeldItemCount(enemyPokemon)).toBeLessThan(enemyHeldItemCt); + expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBe(initialEnemySpeed * 2); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed); }); it("should activate when an item is knocked off", async () => { - await game.classicMode.startBattle(); + await game.classicMode.startBattle([ Species.TREECKO ]); const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; + const enemyHeldItemCt = getHeldItemCount(enemyPokemon); const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); + // Player uses Knock Off and removes the opponent's item game.move.select(Moves.KNOCK_OFF); await game.toNextTurn(); - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); + expect(getHeldItemCount(enemyPokemon)).toBeLessThan(enemyHeldItemCt); + expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBe(initialEnemySpeed * 2); }); it("should activate when an item is stolen via attacking ability", async () => { game.override .ability(Abilities.MAGICIAN) - .startingHeldItems([ - { name: "MULTI_LENS", count: 3 }, - ]); - await game.classicMode.startBattle(); + .startingHeldItems([]); // Remove player's full stacks of held items so it can steal opponent's held items + await game.classicMode.startBattle([ Species.TREECKO ]); const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; + const enemyHeldItemCt = getHeldItemCount(enemyPokemon); const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); - game.move.select(Moves.POPULATION_BOMB); + // Player steals the opponent's item via ability Magician + game.move.select(Moves.FALSE_SWIPE); await game.toNextTurn(); - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); + expect(getHeldItemCount(enemyPokemon)).toBeLessThan(enemyHeldItemCt); + expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBe(initialEnemySpeed * 2); }); it("should activate when an item is stolen via defending ability", async () => { game.override - .startingLevel(45) .enemyAbility(Abilities.PICKPOCKET) - .startingHeldItems([ - { name: "MULTI_LENS", count: 3 }, - { name: "SOUL_DEW", count: 1 }, - { name: "LUCKY_EGG", count: 1 }, - ]); - await game.classicMode.startBattle(); + .enemyHeldItems([]); // Remove opponent's full stacks of held items so it can steal player's held items + await game.classicMode.startBattle([ Species.TREECKO ]); const playerPokemon = game.scene.getPlayerPokemon()!; - playerPokemon.abilityIndex = 2; - const playerHeldItems = playerPokemon.getHeldItems().length; + const playerHeldItems = getHeldItemCount(playerPokemon); const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); - game.move.select(Moves.POPULATION_BOMB); + // Player's item gets stolen via ability Pickpocket + game.move.select(Moves.FALSE_SWIPE); await game.toNextTurn(); - expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems); - expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2); + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); }); it("should activate when an item is stolen via move", async () => { - vi.spyOn(allMoves[Moves.THIEF], "attrs", "get").mockReturnValue([ new StealHeldItemChanceAttr(1.0) ]); // give Thief 100% steal rate - game.override.startingHeldItems([ - { name: "MULTI_LENS", count: 3 }, - ]); - await game.classicMode.startBattle(); + game.override.moveset(Moves.THIEF) + .startingHeldItems([]); // Remove player's full stacks of held items so it can steal opponent's held items + await game.classicMode.startBattle([ Species.TREECKO ]); const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; + const enemyHeldItemCt = getHeldItemCount(enemyPokemon); const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); + // Player uses Thief and steals the opponent's item game.move.select(Moves.THIEF); await game.toNextTurn(); - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); + expect(getHeldItemCount(enemyPokemon)).toBeLessThan(enemyHeldItemCt); + expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBe(initialEnemySpeed * 2); }); it("should activate when an item is stolen via grip claw", async () => { game.override - .startingLevel(5) .startingHeldItems([ - { name: "GRIP_CLAW", count: 5 }, - { name: "MULTI_LENS", count: 3 }, - ]) - .enemyHeldItems([ - { name: "SOUL_DEW", count: 1 }, - { name: "LUCKY_EGG", count: 1 }, - { name: "LEFTOVERS", count: 1 }, { name: "GRIP_CLAW", count: 1 }, - { name: "MULTI_LENS", count: 1 }, - { name: "BERRY", type: BerryType.SITRUS, count: 1 }, - { name: "BERRY", type: BerryType.LUM, count: 1 }, ]); - await game.classicMode.startBattle(); - - const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; - const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); - - while (enemyPokemon.getHeldItems().length === enemyHeldItemCt) { - game.move.select(Moves.POPULATION_BOMB); - await game.toNextTurn(); - } - - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); - }); - - it("should not activate when a neutralizing ability is present", async () => { - game.override.enemyAbility(Abilities.NEUTRALIZING_GAS); - await game.classicMode.startBattle(); + await game.classicMode.startBattle([ Species.TREECKO ]); const playerPokemon = game.scene.getPlayerPokemon()!; - const playerHeldItems = playerPokemon.getHeldItems().length; - const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); + const enemyPokemon = game.scene.getEnemyPokemon()!; + const enemyHeldItemCt = getHeldItemCount(enemyPokemon); + const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); + + // Player steals the opponent's item using Grip Claw game.move.select(Moves.FALSE_SWIPE); await game.toNextTurn(); - expect(playerPokemon.getHeldItems().length).toBeLessThan(playerHeldItems); - expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed); + expect(getHeldItemCount(enemyPokemon)).toBeLessThan(enemyHeldItemCt); + expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBe(initialEnemySpeed * 2); + }); + + it("should not activate when a neutralizing ability is present", async () => { + game.override.enemyAbility(Abilities.NEUTRALIZING_GAS) + .enemyMoveset(Moves.FALSE_SWIPE); + await game.classicMode.startBattle([ Species.TREECKO ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerHeldItems = getHeldItemCount(playerPokemon); + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + + // Player gets hit by False Swipe and eats Sitrus Berry, which should not trigger Unburden + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed); + expect(playerPokemon.getTag(BattlerTagType.UNBURDEN)).toBeUndefined(); }); it("should activate when a move that consumes a berry is used", async () => { - game.override.enemyMoveset([ Moves.STUFF_CHEEKS ]); - await game.classicMode.startBattle(); + game.override.moveset(Moves.STUFF_CHEEKS); + await game.classicMode.startBattle([ Species.TREECKO ]); - const enemyPokemon = game.scene.getEnemyPokemon()!; - const enemyHeldItemCt = enemyPokemon.getHeldItems().length; - const initialEnemySpeed = enemyPokemon.getStat(Stat.SPD); + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerHeldItemCt = getHeldItemCount(playerPokemon); + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + // Player uses Stuff Cheeks and eats its own berry + // Caution: Do not test this using opponent, there is a known issue where opponent can randomly generate with Salac Berry game.move.select(Moves.STUFF_CHEEKS); await game.toNextTurn(); - expect(enemyPokemon.getHeldItems().length).toBeLessThan(enemyHeldItemCt); - expect(enemyPokemon.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialEnemySpeed * 2); + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItemCt); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); }); - it("should deactivate when a neutralizing gas user enters the field", async () => { + it("should deactivate temporarily when a neutralizing gas user is on the field", async () => { game.override .battleType("double") - .moveset([ Moves.SPLASH ]); + .ability(Abilities.NONE); // Disable ability override so that we can properly set abilities below await game.classicMode.startBattle([ Species.TREECKO, Species.MEOWTH, Species.WEEZING ]); - const playerPokemon = game.scene.getPlayerParty(); - const treecko = playerPokemon[0]; - const weezing = playerPokemon[2]; - treecko.abilityIndex = 2; - weezing.abilityIndex = 1; - const playerHeldItems = treecko.getHeldItems().length; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [ treecko, _meowth, weezing ] = game.scene.getPlayerParty(); + treecko.abilityIndex = 2; // Treecko has Unburden + weezing.abilityIndex = 1; // Weezing has Neutralizing Gas + const playerHeldItems = getHeldItemCount(treecko); const initialPlayerSpeed = treecko.getStat(Stat.SPD); + // Turn 1: Treecko gets hit by False Swipe and eats Sitrus Berry, activating Unburden game.move.select(Moves.SPLASH); - game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); await game.forceEnemyMove(Moves.FALSE_SWIPE, 0); await game.forceEnemyMove(Moves.FALSE_SWIPE, 0); await game.phaseInterceptor.to("TurnEndPhase"); - expect(treecko.getHeldItems().length).toBeLessThan(playerHeldItems); - expect(treecko.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed * 2); + expect(getHeldItemCount(treecko)).toBeLessThan(playerHeldItems); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); + // Turn 2: Switch Meowth to Weezing, activating Neutralizing Gas await game.toNextTurn(); game.move.select(Moves.SPLASH); game.doSwitchPokemon(2); await game.phaseInterceptor.to("TurnEndPhase"); - expect(treecko.getHeldItems().length).toBeLessThan(playerHeldItems); - expect(treecko.getEffectiveStat(Stat.SPD)).toBeCloseTo(initialPlayerSpeed); + expect(getHeldItemCount(treecko)).toBeLessThan(playerHeldItems); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed); + + // Turn 3: Switch Weezing to Meowth, deactivating Neutralizing Gas + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + game.doSwitchPokemon(2); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(getHeldItemCount(treecko)).toBeLessThan(playerHeldItems); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); }); + it("should not activate when passing a baton to a teammate switching in", async () => { + game.override.startingHeldItems([{ name: "BATON" }]) + .moveset(Moves.BATON_PASS); + await game.classicMode.startBattle([ Species.TREECKO, Species.PURRLOIN ]); + + const [ treecko, purrloin ] = game.scene.getPlayerParty(); + const initialTreeckoSpeed = treecko.getStat(Stat.SPD); + const initialPurrloinSpeed = purrloin.getStat(Stat.SPD); + const unburdenAttr = treecko.getAbilityAttrs(PostItemLostAbAttr)[0]; + vi.spyOn(unburdenAttr, "applyPostItemLost"); + + // Player uses Baton Pass, which also passes the Baton item + game.move.select(Moves.BATON_PASS); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + expect(getHeldItemCount(treecko)).toBe(0); + expect(getHeldItemCount(purrloin)).toBe(1); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialTreeckoSpeed); + expect(purrloin.getEffectiveStat(Stat.SPD)).toBe(initialPurrloinSpeed); + expect(unburdenAttr.applyPostItemLost).not.toHaveBeenCalled(); + }); + + it("should not speed up a Pokemon after it loses the ability Unburden", async () => { + game.override.enemyMoveset([ Moves.FALSE_SWIPE, Moves.WORRY_SEED ]); + await game.classicMode.startBattle([ Species.PURRLOIN ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerHeldItems = getHeldItemCount(playerPokemon); + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + + // Turn 1: Get hit by False Swipe and eat Sitrus Berry, activating Unburden + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.FALSE_SWIPE); + await game.toNextTurn(); + + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); + + // Turn 2: Get hit by Worry Seed, deactivating Unburden + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WORRY_SEED); + await game.toNextTurn(); + + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed); + }); + + it("should activate when a reviver seed is used", async () => { + game.override.startingHeldItems([{ name: "REVIVER_SEED" }]) + .enemyMoveset([ Moves.WING_ATTACK ]); + await game.classicMode.startBattle([ Species.TREECKO ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const playerHeldItems = getHeldItemCount(playerPokemon); + const initialPlayerSpeed = playerPokemon.getStat(Stat.SPD); + + // Turn 1: Get hit by Wing Attack and faint, activating Reviver Seed + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(getHeldItemCount(playerPokemon)).toBeLessThan(playerHeldItems); + expect(playerPokemon.getEffectiveStat(Stat.SPD)).toBe(initialPlayerSpeed * 2); + }); + + // test for `.bypassFaint()` - singles + it("shouldn't persist when revived normally if activated while fainting", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.THIEF ]); + await game.classicMode.startBattle([ Species.TREECKO, Species.FEEBAS ]); + + const treecko = game.scene.getPlayerPokemon()!; + const treeckoInitialHeldItems = getHeldItemCount(treecko); + const initialSpeed = treecko.getStat(Stat.SPD); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.THIEF); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + + game.doRevivePokemon(1); + game.doSwitchPokemon(1); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!).toBe(treecko); + expect(getHeldItemCount(treecko)).toBeLessThan(treeckoInitialHeldItems); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialSpeed); + }); + + // test for `.bypassFaint()` - doubles + it("shouldn't persist when revived by revival blessing if activated while fainting", async () => { + game.override + .battleType("double") + .enemyMoveset([ Moves.SPLASH, Moves.THIEF ]) + .moveset([ Moves.SPLASH, Moves.REVIVAL_BLESSING ]) + .startingHeldItems([{ name: "WIDE_LENS" }]); + await game.classicMode.startBattle([ Species.TREECKO, Species.FEEBAS, Species.MILOTIC ]); + + const treecko = game.scene.getPlayerField()[0]; + const treeckoInitialHeldItems = getHeldItemCount(treecko); + const initialSpeed = treecko.getStat(Stat.SPD); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.REVIVAL_BLESSING, 1); + await game.forceEnemyMove(Moves.THIEF, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2 ]); + game.doSelectPartyPokemon(0, "MoveEffectPhase"); + await game.toNextTurn(); + + expect(game.scene.getPlayerField()[0]).toBe(treecko); + expect(getHeldItemCount(treecko)).toBeLessThan(treeckoInitialHeldItems); + expect(treecko.getEffectiveStat(Stat.SPD)).toBe(initialSpeed); + }); }); diff --git a/src/test/abilities/wimp_out.test.ts b/src/test/abilities/wimp_out.test.ts index 6f56a2f4e7e..bff68b54c75 100644 --- a/src/test/abilities/wimp_out.test.ts +++ b/src/test/abilities/wimp_out.test.ts @@ -296,7 +296,9 @@ describe("Abilities - Wimp Out", () => { Species.TYRUNT ]); - game.move.select(Moves.SPLASH); + game.scene.getPlayerPokemon()!.hp *= 0.51; + + game.move.select(Moves.ENDURE); await game.phaseInterceptor.to("TurnEndPhase"); confirmNoSwitch(); @@ -611,4 +613,53 @@ describe("Abilities - Wimp Out", () => { confirmNoSwitch(); }); + + it("should not activate on wave X0 bosses", async () => { + game.override.enemyAbility(Abilities.WIMP_OUT) + .startingLevel(5850) + .startingWave(10); + await game.classicMode.startBattle([ Species.GOLISOPOD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + // Use 2 turns of False Swipe due to opponent's health bar shield + game.move.select(Moves.FALSE_SWIPE); + await game.toNextTurn(); + game.move.select(Moves.FALSE_SWIPE); + await game.toNextTurn(); + + const isVisible = enemyPokemon.visible; + const hasFled = enemyPokemon.switchOutStatus; + expect(isVisible && !hasFled).toBe(true); + }); + it("wimp out will not skip battles when triggered in a double battle", async () => { + const wave = 2; + game.override + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.WIMPOD) + .enemyAbility(Abilities.WIMP_OUT) + .moveset([ Moves.MATCHA_GOTCHA, Moves.FALSE_SWIPE ]) + .startingLevel(50) + .enemyLevel(1) + .battleType("double") + .startingWave(wave); + await game.classicMode.startBattle([ + Species.RAICHU, + Species.PIKACHU + ]); + const [ wimpod0, wimpod1 ] = game.scene.getEnemyField(); + + game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.MATCHA_GOTCHA, 1); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(wimpod0.hp).toBeGreaterThan(0); + expect(wimpod0.switchOutStatus).toBe(true); + expect(wimpod0.isFainted()).toBe(false); + expect(wimpod1.isFainted()).toBe(true); + + await game.toNextWave(); + expect(game.scene.currentBattle.waveIndex).toBe(wave + 1); + }); }); diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts index 74575415788..e0cc457c4d5 100644 --- a/src/test/abilities/zen_mode.test.ts +++ b/src/test/abilities/zen_mode.test.ts @@ -1,13 +1,8 @@ -import { BattlerIndex } from "#app/battle"; -import { Status, StatusEffect } from "#app/data/status-effect"; -import { DamagePhase } from "#app/phases/damage-phase"; -import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { Mode } from "#app/ui/ui"; +import { Status } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { Stat } from "#enums/stat"; -import { SwitchType } from "#enums/switch-type"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -33,78 +28,60 @@ describe("Abilities - ZEN MODE", () => { game = new GameManager(phaserGame); game.override .battleType("single") - .enemySpecies(Species.RATTATA) - .enemyAbility(Abilities.HYDRATION) + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyLevel(5) .ability(Abilities.ZEN_MODE) - .startingLevel(100) .moveset(Moves.SPLASH) - .enemyMoveset(Moves.TACKLE); + .enemyMoveset(Moves.SEISMIC_TOSS); }); it("shouldn't change form when taking damage if not dropping below 50% HP", async () => { await game.classicMode.startBattle([ Species.DARMANITAN ]); - const player = game.scene.getPlayerPokemon()!; - player.stats[Stat.HP] = 100; - player.hp = 100; - expect(player.formIndex).toBe(baseForm); + const darmanitan = game.scene.getPlayerPokemon()!; + expect(darmanitan.formIndex).toBe(baseForm); game.move.select(Moves.SPLASH); - await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); - await game.phaseInterceptor.to("BerryPhase"); + await game.toNextTurn(); - expect(player.hp).toBeLessThan(100); - expect(player.formIndex).toBe(baseForm); + expect(darmanitan.getHpRatio()).toBeLessThan(1); + expect(darmanitan.getHpRatio()).toBeGreaterThan(0.5); + expect(darmanitan.formIndex).toBe(baseForm); }); it("should change form when falling below 50% HP", async () => { await game.classicMode.startBattle([ Species.DARMANITAN ]); - const player = game.scene.getPlayerPokemon()!; - player.stats[Stat.HP] = 1000; - player.hp = 100; - expect(player.formIndex).toBe(baseForm); + const darmanitan = game.scene.getPlayerPokemon()!; + darmanitan.hp = (darmanitan.getMaxHp() / 2) + 1; + expect(darmanitan.formIndex).toBe(baseForm); game.move.select(Moves.SPLASH); + await game.toNextTurn(); - await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); - await game.phaseInterceptor.to("QuietFormChangePhase"); - await game.phaseInterceptor.to("TurnInitPhase", false); - - expect(player.hp).not.toBe(100); - expect(player.formIndex).toBe(zenForm); + expect(darmanitan.getHpRatio()).toBeLessThan(0.5); + expect(darmanitan.formIndex).toBe(zenForm); }); it("should stay zen mode when fainted", async () => { await game.classicMode.startBattle([ Species.DARMANITAN, Species.CHARIZARD ]); - const player = game.scene.getPlayerPokemon()!; - player.stats[Stat.HP] = 1000; - player.hp = 100; - expect(player.formIndex).toBe(baseForm); + const darmanitan = game.scene.getPlayerPokemon()!; + darmanitan.hp = (darmanitan.getMaxHp() / 2) + 1; + expect(darmanitan.formIndex).toBe(baseForm); game.move.select(Moves.SPLASH); + await game.toNextTurn(); - await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); - await game.phaseInterceptor.to(DamagePhase, false); - const damagePhase = game.scene.getCurrentPhase() as DamagePhase; - damagePhase.updateAmount(80); - await game.phaseInterceptor.to("QuietFormChangePhase"); + expect(darmanitan.getHpRatio()).toBeLessThan(0.5); + expect(darmanitan.formIndex).toBe(zenForm); - expect(player.hp).not.toBe(100); - expect(player.formIndex).toBe(zenForm); - - await game.killPokemon(player); - expect(player.isFainted()).toBe(true); - - await game.phaseInterceptor.to("TurnStartPhase"); - game.onNextPrompt("SwitchPhase", Mode.PARTY, () => { - game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, SwitchType.SWITCH, 0, 1, false)); - game.scene.ui.setMode(Mode.MESSAGE); - }); - game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => { - game.endPhase(); - }); - await game.phaseInterceptor.to("PostSummonPhase"); + game.move.select(Moves.SPLASH); + await game.killPokemon(darmanitan); + game.doSelectPartyPokemon(1); + await game.toNextTurn(); + expect(darmanitan.isFainted()).toBe(true); expect(game.scene.getPlayerParty()[1].formIndex).toBe(zenForm); }); @@ -116,7 +93,8 @@ describe("Abilities - ZEN MODE", () => { await game.classicMode.startBattle([ Species.MAGIKARP, Species.DARMANITAN ]); - const darmanitan = game.scene.getPlayerParty().find((p) => p.species.speciesId === Species.DARMANITAN)!; + const darmanitan = game.scene.getPlayerParty()[1]; + darmanitan.hp = 1; expect(darmanitan.formIndex).toBe(zenForm); darmanitan.hp = 0; @@ -125,9 +103,7 @@ describe("Abilities - ZEN MODE", () => { game.move.select(Moves.SPLASH); await game.doKillOpponents(); - await game.phaseInterceptor.to("TurnEndPhase"); - game.doSelectModifier(); - await game.phaseInterceptor.to("QuietFormChangePhase"); + await game.toNextWave(); expect(darmanitan.formIndex).toBe(baseForm); }); diff --git a/src/test/abilities/zero_to_hero.test.ts b/src/test/abilities/zero_to_hero.test.ts index 160b43abf1e..5702f73e6c4 100644 --- a/src/test/abilities/zero_to_hero.test.ts +++ b/src/test/abilities/zero_to_hero.test.ts @@ -1,9 +1,10 @@ -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/account.test.ts b/src/test/account.test.ts index 8c36f2cd953..0f49014c377 100644 --- a/src/test/account.test.ts +++ b/src/test/account.test.ts @@ -1,7 +1,7 @@ import * as battleScene from "#app/battle-scene"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { describe, expect, it, vi } from "vitest"; import { initLoggedInUser, loggedInUser, updateUserInfo } from "../account"; -import * as utils from "../utils"; describe("account", () => { describe("initLoggedInUser", () => { @@ -27,17 +27,16 @@ describe("account", () => { it("should fetch user info from the API if bypassLogin is false", async () => { vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false); - vi.spyOn(utils, "apiFetch").mockResolvedValue( - new Response( - JSON.stringify({ - username: "test", - lastSessionSlot: 99, - }), - { - status: 200, - } - ) - ); + vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([ + { + username: "test", + lastSessionSlot: 99, + discordId: "", + googleId: "", + hasAdminRole: false, + }, + 200, + ]); const [ success, status ] = await updateUserInfo(); @@ -49,9 +48,7 @@ describe("account", () => { it("should handle resolved API errors", async () => { vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false); - vi.spyOn(utils, "apiFetch").mockResolvedValue( - new Response(null, { status: 401 }) - ); + vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([ null, 401 ]); const [ success, status ] = await updateUserInfo(); @@ -59,16 +56,14 @@ describe("account", () => { expect(status).toBe(401); }); - it("should handle rejected API errors", async () => { - const consoleErrorSpy = vi.spyOn(console, "error"); + it("should handle 500 API errors", async () => { vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(false); - vi.spyOn(utils, "apiFetch").mockRejectedValue(new Error("Api failed!")); + vi.spyOn(pokerogueApi.account, "getInfo").mockResolvedValue([ null, 500 ]); const [ success, status ] = await updateUserInfo(); expect(success).toBe(false); expect(status).toBe(500); - expect(consoleErrorSpy).toHaveBeenCalled(); }); }); }); diff --git a/src/test/arena/weather_fog.test.ts b/src/test/arena/weather_fog.test.ts index db591dda2b8..8c1fcb1e3a4 100644 --- a/src/test/arena/weather_fog.test.ts +++ b/src/test/arena/weather_fog.test.ts @@ -1,9 +1,9 @@ import { allMoves } from "#app/data/move"; -import { WeatherType } from "#app/data/weather"; import { Abilities } from "#app/enums/abilities"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/src/test/arena/weather_hail.test.ts b/src/test/arena/weather_hail.test.ts index b8f4276a3d9..0b267550d75 100644 --- a/src/test/arena/weather_hail.test.ts +++ b/src/test/arena/weather_hail.test.ts @@ -1,10 +1,10 @@ -import { WeatherType } from "#app/data/weather"; +import { BattlerIndex } from "#app/battle"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { BattlerIndex } from "#app/battle"; describe("Weather - Hail", () => { let phaserGame: Phaser.Game; diff --git a/src/test/arena/weather_sandstorm.test.ts b/src/test/arena/weather_sandstorm.test.ts index 09d0c1d2b77..17ccfdee94b 100644 --- a/src/test/arena/weather_sandstorm.test.ts +++ b/src/test/arena/weather_sandstorm.test.ts @@ -1,8 +1,8 @@ -import { WeatherType } from "#app/data/weather"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts index 656cc62ac59..d2b074acce0 100644 --- a/src/test/battle/battle.test.ts +++ b/src/test/battle/battle.test.ts @@ -3,7 +3,7 @@ import { Stat } from "#enums/stat"; import { GameModes, getGameMode } from "#app/game-mode"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { LoginPhase } from "#app/phases/login-phase"; @@ -267,7 +267,7 @@ describe("Test Battle Phase", () => { ]); game.move.select(moveToUse); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); await game.killPokemon(game.scene.currentBattle.enemyParty[0]); expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true); await game.phaseInterceptor.to(VictoryPhase, false); diff --git a/src/test/battle/damage_calculation.test.ts b/src/test/battle/damage_calculation.test.ts index e6ecbe4646f..e6aca828156 100644 --- a/src/test/battle/damage_calculation.test.ts +++ b/src/test/battle/damage_calculation.test.ts @@ -102,7 +102,7 @@ describe("Battle Mechanics - Damage Calculation", () => { game.move.select(Moves.JUMP_KICK); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(shedinja.hp).toBe(shedinja.getMaxHp() - 1); }); diff --git a/src/test/battle/double_battle.test.ts b/src/test/battle/double_battle.test.ts index a7585d55bab..b48f2a96a5b 100644 --- a/src/test/battle/double_battle.test.ts +++ b/src/test/battle/double_battle.test.ts @@ -1,13 +1,18 @@ -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; +import { Abilities } from "#enums/abilities"; +import { GameModes, getGameMode } from "#app/game-mode"; import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Double Battles", () => { + const DOUBLE_CHANCE = 8; // Normal chance of double battle is 1/8 + let phaserGame: Phaser.Game; let game: GameManager; @@ -55,4 +60,40 @@ describe("Double Battles", () => { await game.phaseInterceptor.to(TurnInitPhase); expect(game.scene.getPlayerField().filter(p => !p.isFainted())).toHaveLength(2); }, 20000); + + it("randomly chooses between single and double battles if there is no battle type override", async () => { + let rngSweepProgress = 0; // Will simulate RNG rolls by slowly increasing from 0 to 1 + let doubleCount = 0; + let singleCount = 0; + + vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => { + return rngSweepProgress * (max - min) + min; + }); + + game.override.enemyMoveset(Moves.SPLASH) + .moveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH); + + // Play through endless, waves 1 to 9, counting number of double battles from waves 2 to 9 + await game.classicMode.startBattle([ Species.BULBASAUR ]); + game.scene.gameMode = getGameMode(GameModes.ENDLESS); + + for (let i = 0; i < DOUBLE_CHANCE; i++) { + rngSweepProgress = (i + 0.5) / DOUBLE_CHANCE; + + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.toNextWave(); + + if (game.scene.getEnemyParty().length === 1) { + singleCount++; + } else if (game.scene.getEnemyParty().length === 2) { + doubleCount++; + } + } + + expect(doubleCount).toBe(1); + expect(singleCount).toBe(DOUBLE_CHANCE - 1); + }); }); diff --git a/src/test/battle/inverse_battle.test.ts b/src/test/battle/inverse_battle.test.ts index 110f92d15b7..0bda678bbd3 100644 --- a/src/test/battle/inverse_battle.test.ts +++ b/src/test/battle/inverse_battle.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Challenges } from "#enums/challenges"; diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index ebd92dc6401..9efce220fe8 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -1,9 +1,8 @@ import BattleScene from "#app/battle-scene"; import { describe, expect, it, vi } from "vitest"; import Pokemon from "#app/field/pokemon"; -import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Stat } from "#enums/stat"; vi.mock("#app/battle-scene.js"); @@ -33,30 +32,4 @@ describe("BattlerTag - OctolockTag", () => { it ("traps its target (extends TrappedTag)", async () => { expect(new OctolockTag(1)).toBeInstanceOf(TrappedTag); }); - - it("can be added to pokemon who are not octolocked", async => { - const mockPokemon = { - getTag: vi.fn().mockReturnValue(undefined) as Pokemon["getTag"], - } as Pokemon; - - const subject = new OctolockTag(1); - - expect(subject.canAdd(mockPokemon)).toBeTruthy(); - - expect(mockPokemon.getTag).toHaveBeenCalledTimes(1); - expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK); - }); - - it("cannot be added to pokemon who are octolocked", async => { - const mockPokemon = { - getTag: vi.fn().mockReturnValue(new BattlerTag(null!, null!, null!, null!)) as Pokemon["getTag"], - } as Pokemon; - - const subject = new OctolockTag(1); - - expect(subject.canAdd(mockPokemon)).toBeFalsy(); - - expect(mockPokemon.getTag).toHaveBeenCalledTimes(1); - expect(mockPokemon.getTag).toHaveBeenCalledWith(BattlerTagType.OCTOLOCK); - }); }); diff --git a/src/test/daily_mode.test.ts b/src/test/daily_mode.test.ts index 2a88ce10ae7..3e70cc2d8a7 100644 --- a/src/test/daily_mode.test.ts +++ b/src/test/daily_mode.test.ts @@ -1,11 +1,12 @@ -import { MapModifier } from "#app/modifier/modifier"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import GameManager from "./utils/gameManager"; -import { Moves } from "#app/enums/moves"; import { Biome } from "#app/enums/biome"; -import { Mode } from "#app/ui/ui"; +import { Moves } from "#app/enums/moves"; +import { MapModifier } from "#app/modifier/modifier"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { Species } from "#enums/species"; +import { Mode } from "#app/ui/ui"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import GameManager from "#app/test/utils/gameManager"; //const TIMEOUT = 20 * 1000; @@ -21,6 +22,7 @@ describe("Daily Mode", () => { beforeEach(() => { game = new GameManager(phaserGame); + vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed"); }); afterEach(() => { @@ -32,7 +34,7 @@ describe("Daily Mode", () => { const party = game.scene.getPlayerParty(); expect(party).toHaveLength(3); - party.forEach(pkm => { + party.forEach((pkm) => { expect(pkm.level).toBe(20); expect(pkm.moveset.length).toBeGreaterThan(0); }); @@ -63,6 +65,7 @@ describe("Shop modifications", async () => { game.modifiers .addCheck("EVIOLITE") .addCheck("MINI_BLACK_HOLE"); + vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed"); }); afterEach(() => { diff --git a/src/test/data/status-effect.test.ts b/src/test/data/status-effect.test.ts index 1b1a97fc51f..4831e8de5de 100644 --- a/src/test/data/status-effect.test.ts +++ b/src/test/data/status-effect.test.ts @@ -1,6 +1,5 @@ import { Status, - StatusEffect, getStatusEffectActivationText, getStatusEffectDescriptor, getStatusEffectHealText, @@ -11,6 +10,7 @@ import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import { mockI18next } from "#test/utils/testUtils"; import i18next from "i18next"; diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index aebb196ff5e..1a33b4eff7c 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -1,5 +1,4 @@ import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; -import { speciesStarterCosts } from "#app/data/balance/starters"; import { Egg, getLegendaryGachaSpeciesForTimestamp, getValidLegendaryGachaSpecies } from "#app/data/egg"; import { allSpecies } from "#app/data/pokemon-species"; import { EggSourceType } from "#app/enums/egg-source-types"; @@ -33,28 +32,28 @@ describe("Egg Generation Tests", () => { await game.importData("src/test/utils/saves/everything.prsv"); }); - it("should return Arceus for the 10th of June", () => { + it("should return Kyogre for the 10th of June", () => { const scene = game.scene; const timestamp = new Date(2024, 5, 10, 15, 0, 0, 0).getTime(); - const expectedSpecies = Species.ARCEUS; + const expectedSpecies = Species.KYOGRE; const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); expect(result).toBe(expectedSpecies); }); - it("should return Arceus for the 10th of July", () => { + it("should return Kyogre for the 10th of July", () => { const scene = game.scene; const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime(); - const expectedSpecies = Species.ARCEUS; + const expectedSpecies = Species.KYOGRE; const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); expect(result).toBe(expectedSpecies); }); - it("should hatch an Arceus around half the time. Set from legendary gacha", async () => { + it("should hatch a Kyogre around half the time. Set from legendary gacha", async () => { const scene = game.scene; const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime(); - const expectedSpecies = Species.ARCEUS; + const expectedSpecies = Species.KYOGRE; let gachaSpeciesCount = 0; for (let i = 0; i < EGG_HATCH_COUNT; i++) { @@ -385,23 +384,4 @@ describe("Egg Generation Tests", () => { expect(diffShiny).toBe(true); expect(diffAbility).toBe(true); }); - - // For now, we are using this test to detect oversights in egg tiers. - // Delete this test if the balance team rebalances species costs independently of egg tiers. - it("should have correct egg tiers based on species costs", () => { - const getExpectedEggTier = (starterCost) => - starterCost <= 3 ? EggTier.COMMON - : starterCost <= 5 ? EggTier.RARE - : starterCost <= 7 ? EggTier.EPIC - : EggTier.LEGENDARY; - - allSpecies.forEach(pokemonSpecies => { - const rootSpecies = pokemonSpecies.getRootSpeciesId(); - const speciesCost = speciesStarterCosts[rootSpecies]; - const expectedEggTier = getExpectedEggTier(speciesCost); - const actualEggTier = speciesEggTiers[rootSpecies]; - - expect(actualEggTier).toBe(expectedEggTier); - }); - }); }); diff --git a/src/test/field/pokemon.test.ts b/src/test/field/pokemon.test.ts index ce9862e22e4..0bfbd03e9d9 100644 --- a/src/test/field/pokemon.test.ts +++ b/src/test/field/pokemon.test.ts @@ -1,7 +1,7 @@ import { Species } from "#app/enums/species"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "../utils/gameManager"; -import { PokeballType } from "#app/enums/pokeball"; +import { PokeballType } from "#enums/pokeball"; import BattleScene from "#app/battle-scene"; import { Moves } from "#app/enums/moves"; diff --git a/src/test/final_boss.test.ts b/src/test/final_boss.test.ts index de2cddff944..5540d9511e4 100644 --- a/src/test/final_boss.test.ts +++ b/src/test/final_boss.test.ts @@ -1,12 +1,12 @@ -import { StatusEffect } from "#app/data/status-effect"; -import { Abilities } from "#app/enums/abilities"; -import { Biome } from "#app/enums/biome"; -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; import { GameModes } from "#app/game-mode"; import { TurnHeldItemTransferModifier } from "#app/modifier/modifier"; +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import GameManager from "./utils/gameManager"; const FinalWave = { Classic: 200, diff --git a/src/test/items/leftovers.test.ts b/src/test/items/leftovers.test.ts index cfbf7c2f734..672151d97cb 100644 --- a/src/test/items/leftovers.test.ts +++ b/src/test/items/leftovers.test.ts @@ -1,4 +1,4 @@ -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -48,7 +48,7 @@ describe("Items - Leftovers", () => { game.move.select(Moves.SPLASH); // We should have less hp after the attack - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(leadPokemon.hp).toBeLessThan(leadPokemon.getMaxHp()); const leadHpAfterDamage = leadPokemon.hp; diff --git a/src/test/items/multi_lens.test.ts b/src/test/items/multi_lens.test.ts new file mode 100644 index 00000000000..bd586878fce --- /dev/null +++ b/src/test/items/multi_lens.test.ts @@ -0,0 +1,212 @@ +import { BattlerIndex } from "#app/battle"; +import { Stat } from "#enums/stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Items - Multi Lens", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.TACKLE, Moves.TRAILBLAZE, Moves.TACHYON_CUTTER, Moves.FUTURE_SIGHT ]) + .ability(Abilities.BALL_FETCH) + .startingHeldItems([{ name: "MULTI_LENS" }]) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .startingLevel(99) // Check for proper rounding on Seismic Toss damage reduction + .enemyLevel(99); + }); + + it.each([ + { stackCount: 1, firstHitDamage: 0.75 }, + { stackCount: 2, firstHitDamage: 0.50 } + ])("$stackCount count: should deal {$firstHitDamage}x damage on the first hit, then hit $stackCount times for 0.25x", + async ({ stackCount, firstHitDamage }) => { + game.override.startingHeldItems([{ name: "MULTI_LENS", count: stackCount }]); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + const spy = vi.spyOn(enemyPokemon, "getAttackDamage"); + vi.spyOn(enemyPokemon, "getBaseDamage").mockReturnValue(100); + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + const damageResults = spy.mock.results.map(result => result.value?.damage); + + expect(damageResults).toHaveLength(1 + stackCount); + expect(damageResults[0]).toBe(firstHitDamage * 100); + damageResults.slice(1).forEach(dmg => expect(dmg).toBe(25)); + }); + + it("should stack additively with Parental Bond", async () => { + game.override.ability(Abilities.PARENTAL_BOND); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TACKLE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + expect(playerPokemon.turnData.hitCount).toBe(3); + }); + + it("should apply secondary effects on each hit", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TRAILBLAZE); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(playerPokemon.getStatStage(Stat.SPD)).toBe(2); + }); + + it("should not enhance multi-hit moves", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.TACHYON_CUTTER); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(playerPokemon.turnData.hitCount).toBe(2); + }); + + it("should enhance multi-target moves", async () => { + game.override + .battleType("double") + .moveset([ Moves.SWIFT, Moves.SPLASH ]); + + await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]); + + const [ magikarp, ] = game.scene.getPlayerField(); + + game.move.select(Moves.SWIFT, 0); + game.move.select(Moves.SPLASH, 1); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(magikarp.turnData.hitCount).toBe(2); + }); + + it("should enhance fixed-damage moves while also applying damage reduction", async () => { + game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]) + .moveset(Moves.SEISMIC_TOSS); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + const spy = vi.spyOn(enemyPokemon, "getAttackDamage"); + + game.move.select(Moves.SEISMIC_TOSS); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to("MoveEndPhase"); + const damageResults = spy.mock.results.map(result => result.value?.damage); + + expect(damageResults).toHaveLength(2); + expect(damageResults[0]).toBe(Math.floor(playerPokemon.level * 0.75)); + expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25)); + }); + + it("should result in correct damage for hp% attacks with 1 lens", async () => { + game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]) + .moveset(Moves.SUPER_FANG) + .ability(Abilities.COMPOUND_EYES) + .enemyLevel(1000) + .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SUPER_FANG); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); + }); + + it("should result in correct damage for hp% attacks with 2 lenses", async () => { + game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) + .moveset(Moves.SUPER_FANG) + .ability(Abilities.COMPOUND_EYES) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(1000) + .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SUPER_FANG); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5); + }); + + it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => { + game.override.startingHeldItems([{ name: "MULTI_LENS", count: 2 }]) + .moveset(Moves.SUPER_FANG) + .ability(Abilities.PARENTAL_BOND) + .passiveAbility(Abilities.COMPOUND_EYES) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(1000) + .enemySpecies(Species.BLISSEY); // allows for unrealistically high levels of accuracy + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.SUPER_FANG); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEndPhase"); + expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5); + }); + + it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => { + game.override.enemyLevel(1000); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + vi.spyOn(enemyPokemon, "damageAndUpdate"); + + game.move.select(Moves.FUTURE_SIGHT); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + game.doSwitchPokemon(2); + await game.toNextTurn(); + + // TODO: Update hit count to 1 once Future Sight is fixed to not activate held items if user is off the field + expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 583e302126c..6918d7f34f0 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -1,8 +1,8 @@ -import { StatusEffect } from "#app/data/status-effect"; import i18next from "#app/plugins/i18n"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/src/test/misc.test.ts b/src/test/misc.test.ts index 3335c4c5523..ae91a5014d9 100644 --- a/src/test/misc.test.ts +++ b/src/test/misc.test.ts @@ -1,4 +1,4 @@ -import { apiFetch } from "#app/utils"; +// import { apiFetch } from "#app/utils"; import GameManager from "#test/utils/gameManager"; import { waitUntil } from "#test/utils/gameManagerUtils"; import Phaser from "phaser"; @@ -35,18 +35,18 @@ describe("Test misc", () => { expect(spy).toHaveBeenCalled(); }); - it("test apifetch mock async", async () => { - const spy = vi.fn(); - await apiFetch("https://localhost:8080/account/info").then(response => { - expect(response.status).toBe(200); - expect(response.ok).toBe(true); - return response.json(); - }).then(data => { - spy(); // Call the spy function - expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 }); - }); - expect(spy).toHaveBeenCalled(); - }); + // it.skip("test apifetch mock async", async () => { + // const spy = vi.fn(); + // await apiFetch("https://localhost:8080/account/info").then(response => { + // expect(response.status).toBe(200); + // expect(response.ok).toBe(true); + // return response.json(); + // }).then(data => { + // spy(); // Call the spy function + // expect(data).toEqual({ "username": "greenlamp", "lastSessionSlot": 0 }); + // }); + // expect(spy).toHaveBeenCalled(); + // }); it("test fetch mock sync", async () => { const response = await fetch("https://localhost:8080/account/info"); diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index e68117a2f59..2d7484b4eb5 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -1,13 +1,13 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import Move, { allMoves } from "#app/data/move"; -import { WeatherType } from "#app/data/weather"; -import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { NumberHolder } from "#app/utils"; +import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/beat_up.test.ts b/src/test/moves/beat_up.test.ts index a0129621f0e..41e5b63471f 100644 --- a/src/test/moves/beat_up.test.ts +++ b/src/test/moves/beat_up.test.ts @@ -74,29 +74,4 @@ describe("Moves - Beat Up", () => { expect(playerPokemon.turnData.hitCount).toBe(5); } ); - - it( - "should hit twice for each player Pokemon if the user has Multi-Lens", - async () => { - game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); - await game.startBattle([ Species.MAGIKARP, Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.PIKACHU, Species.EEVEE ]); - - const playerPokemon = game.scene.getPlayerPokemon()!; - const enemyPokemon = game.scene.getEnemyPokemon()!; - let enemyStartingHp = enemyPokemon.hp; - - game.move.select(Moves.BEAT_UP); - - await game.phaseInterceptor.to(MoveEffectPhase); - - expect(playerPokemon.turnData.hitCount).toBe(12); - expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); - - while (playerPokemon.turnData.hitsLeft > 0) { - enemyStartingHp = enemyPokemon.hp; - await game.phaseInterceptor.to(MoveEffectPhase); - expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); - } - } - ); }); diff --git a/src/test/moves/camouflage.test.ts b/src/test/moves/camouflage.test.ts index acf37635c47..5773afffcc3 100644 --- a/src/test/moves/camouflage.test.ts +++ b/src/test/moves/camouflage.test.ts @@ -2,7 +2,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TerrainType } from "#app/data/terrain"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { BattlerIndex } from "#app/battle"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts index 88c8c8cf011..3fbbb7b0aaf 100644 --- a/src/test/moves/ceaseless_edge.test.ts +++ b/src/test/moves/ceaseless_edge.test.ts @@ -34,7 +34,7 @@ describe("Moves - Ceaseless Edge", () => { game.override.startingLevel(100); game.override.enemyLevel(100); game.override.moveset([ Moves.CEASELESS_EDGE, Moves.SPLASH, Moves.ROAR ]); - game.override.enemyMoveset([ Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH ]); + game.override.enemyMoveset(Moves.SPLASH); vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100); }); @@ -42,7 +42,7 @@ describe("Moves - Ceaseless Edge", () => { test( "move should hit and apply spikes", async () => { - await game.startBattle([ Species.ILLUMISE ]); + await game.classicMode.startBattle([ Species.ILLUMISE ]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -67,7 +67,7 @@ describe("Moves - Ceaseless Edge", () => { "move should hit twice with multi lens and apply two layers of spikes", async () => { game.override.startingHeldItems([{ name: "MULTI_LENS" }]); - await game.startBattle([ Species.ILLUMISE ]); + await game.classicMode.startBattle([ Species.ILLUMISE ]); const enemyPokemon = game.scene.getEnemyPokemon()!; @@ -92,9 +92,9 @@ describe("Moves - Ceaseless Edge", () => { "trainer - move should hit twice, apply two layers of spikes, force switch opponent - opponent takes damage", async () => { game.override.startingHeldItems([{ name: "MULTI_LENS" }]); - game.override.startingWave(5); + game.override.startingWave(25); - await game.startBattle([ Species.ILLUMISE ]); + await game.classicMode.startBattle([ Species.ILLUMISE ]); game.move.select(Moves.CEASELESS_EDGE); await game.phaseInterceptor.to(MoveEffectPhase, false); @@ -102,7 +102,7 @@ describe("Moves - Ceaseless Edge", () => { const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); - await game.phaseInterceptor.to(TurnEndPhase, false); + await game.toNextTurn(); const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); expect(tagAfter.layers).toBe(2); diff --git a/src/test/moves/dragon_cheer.test.ts b/src/test/moves/dragon_cheer.test.ts index 37b74d44360..750f09214ca 100644 --- a/src/test/moves/dragon_cheer.test.ts +++ b/src/test/moves/dragon_cheer.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts index 595f347a6b5..d5536ff9d2f 100644 --- a/src/test/moves/dragon_rage.test.ts +++ b/src/test/moves/dragon_rage.test.ts @@ -1,8 +1,7 @@ import { Stat } from "#enums/stat"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Species } from "#app/enums/species"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { modifierTypes } from "#app/modifier/modifier-type"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -114,14 +113,4 @@ describe("Moves - Dragon Rage", () => { expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage); }); - - it("ignores multi hit", async () => { - game.override.disableCrits(); - game.scene.addModifier(modifierTypes.MULTI_LENS().newModifier(partyPokemon), false); - - game.move.select(Moves.DRAGON_RAGE); - await game.phaseInterceptor.to(TurnEndPhase); - - expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage); - }); }); diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index 6b3e669f770..96db67279d3 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -1,5 +1,9 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; +import { Status } from "#app/data/status-effect"; +import { Challenges } from "#enums/challenges"; +import { StatusEffect } from "#enums/status-effect"; +import { Type } from "#enums/type"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -193,4 +197,122 @@ describe("Moves - Dragon Tail", () => { expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2)); expect(game.scene.getPlayerField().length).toBe(1); }); + + it("should force switches randomly", async () => { + game.override.enemyMoveset(Moves.DRAGON_TAIL) + .startingLevel(100) + .enemyLevel(1); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const [ bulbasaur, charmander, squirtle ] = game.scene.getPlayerParty(); + + // Turn 1: Mock an RNG call that calls for switching to 1st backup Pokemon (Charmander) + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.toNextTurn(); + + expect(bulbasaur.isOnField()).toBe(false); + expect(charmander.isOnField()).toBe(true); + expect(squirtle.isOnField()).toBe(false); + expect(bulbasaur.getInverseHp()).toBeGreaterThan(0); + + // Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle) + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min + 1; + }); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(bulbasaur.isOnField()).toBe(false); + expect(charmander.isOnField()).toBe(false); + expect(squirtle.isOnField()).toBe(true); + expect(charmander.getInverseHp()).toBeGreaterThan(0); + }); + + it("should not force a switch to a challenge-ineligible Pokemon", async () => { + game.override.enemyMoveset(Moves.DRAGON_TAIL) + .startingLevel(100) + .enemyLevel(1); + // Mono-Water challenge, Eevee is ineligible + game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, Type.WATER + 1, 0); + await game.challengeMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]); + + const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty(); + + // Turn 1: Mock an RNG call that would normally call for switching to Eevee, but it is ineligible + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(false); + expect(eevee.isOnField()).toBe(false); + expect(toxapex.isOnField()).toBe(true); + expect(primarina.isOnField()).toBe(false); + expect(lapras.getInverseHp()).toBeGreaterThan(0); + }); + + it("should not force a switch to a fainted Pokemon", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.DRAGON_TAIL ]) + .startingLevel(100) + .enemyLevel(1); + await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]); + + const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty(); + + // Turn 1: Eevee faints + eevee.hp = 0; + eevee.status = new Status(StatusEffect.FAINT); + expect(eevee.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(false); + expect(eevee.isOnField()).toBe(false); + expect(toxapex.isOnField()).toBe(true); + expect(primarina.isOnField()).toBe(false); + expect(lapras.getInverseHp()).toBeGreaterThan(0); + }); + + it("should not force a switch if there are no available Pokemon to switch into", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.DRAGON_TAIL ]) + .startingLevel(100) + .enemyLevel(1); + await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE ]); + + const [ lapras, eevee ] = game.scene.getPlayerParty(); + + // Turn 1: Eevee faints + eevee.hp = 0; + eevee.status = new Status(StatusEffect.FAINT); + expect(eevee.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.DRAGON_TAIL); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(true); + expect(eevee.isOnField()).toBe(false); + expect(lapras.getInverseHp()).toBeGreaterThan(0); + }); }); diff --git a/src/test/moves/dynamax_cannon.test.ts b/src/test/moves/dynamax_cannon.test.ts index 001f986bd52..269374f7514 100644 --- a/src/test/moves/dynamax_cannon.test.ts +++ b/src/test/moves/dynamax_cannon.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -51,7 +51,7 @@ describe("Moves - Dynamax Cannon", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -65,7 +65,7 @@ describe("Moves - Dynamax Cannon", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -82,7 +82,7 @@ describe("Moves - Dynamax Cannon", () => { expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(120); }, 20000); @@ -99,7 +99,7 @@ describe("Moves - Dynamax Cannon", () => { expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(140); }, 20000); @@ -116,7 +116,7 @@ describe("Moves - Dynamax Cannon", () => { expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(160); }, 20000); @@ -133,7 +133,7 @@ describe("Moves - Dynamax Cannon", () => { expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(180); }, 20000); @@ -150,7 +150,7 @@ describe("Moves - Dynamax Cannon", () => { expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -165,7 +165,7 @@ describe("Moves - Dynamax Cannon", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); }); diff --git a/src/test/moves/effectiveness.test.ts b/src/test/moves/effectiveness.test.ts index d1903c79844..5ad258d7990 100644 --- a/src/test/moves/effectiveness.test.ts +++ b/src/test/moves/effectiveness.test.ts @@ -1,12 +1,12 @@ import { allMoves } from "#app/data/move"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#app/data/trainer-config"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import * as Messages from "#app/messages"; -import { TerastallizeModifier } from "#app/modifier/modifier"; +import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; @@ -15,15 +15,17 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void { // Suppress getPokemonNameWithAffix because it calls on a null battle spec vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue(""); - game.override.enemyAbility(targetAbility); - - if (teraType !== undefined) { - game.override.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]); - } + game.override + .enemyAbility(targetAbility) + .enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]); const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5); const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE); + if (teraType !== undefined) { + overrideHeldItems(game.scene, target, false); + } + expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected); user.destroy(); target.destroy(); diff --git a/src/test/moves/electrify.test.ts b/src/test/moves/electrify.test.ts index 5d15a825688..8015dd0a74d 100644 --- a/src/test/moves/electrify.test.ts +++ b/src/test/moves/electrify.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/electro_shot.test.ts b/src/test/moves/electro_shot.test.ts index 1373b4941eb..283154b3408 100644 --- a/src/test/moves/electro_shot.test.ts +++ b/src/test/moves/electro_shot.test.ts @@ -98,7 +98,7 @@ describe("Moves - Electro Shot", () => { game.move.select(Moves.ELECTRO_SHOT); await game.phaseInterceptor.to("MoveEndPhase"); - expect(playerPokemon.turnData.hitCount).toBe(2); + expect(playerPokemon.turnData.hitCount).toBe(1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); }); }); diff --git a/src/test/moves/encore.test.ts b/src/test/moves/encore.test.ts new file mode 100644 index 00000000000..7d8dc9658bf --- /dev/null +++ b/src/test/moves/encore.test.ts @@ -0,0 +1,116 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { BattlerIndex } from "#app/battle"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Encore", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.ENCORE ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH, Moves.TACKLE ]) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should prevent the target from using any move except the last used move", async () => { + await game.classicMode.startBattle([ Species.SNORLAX ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.ENCORE); + await game.forceEnemyMove(Moves.SPLASH); + + await game.toNextTurn(); + expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeDefined(); + + game.move.select(Moves.SPLASH); + // The enemy AI would normally be inclined to use Tackle, but should be + // forced into using Splash. + await game.phaseInterceptor.to("BerryPhase", false); + + expect(enemyPokemon.getLastXMoves().every(turnMove => turnMove.move === Moves.SPLASH)).toBeTruthy(); + }); + + describe("should fail against the following moves:", () => { + it.each([ + { moveId: Moves.TRANSFORM, name: "Transform", delay: false }, + { moveId: Moves.MIMIC, name: "Mimic", delay: true }, + { moveId: Moves.SKETCH, name: "Sketch", delay: true }, + { moveId: Moves.ENCORE, name: "Encore", delay: false }, + { moveId: Moves.STRUGGLE, name: "Struggle", delay: false } + ])("$name", async ({ moveId, delay }) => { + game.override.enemyMoveset(moveId); + + await game.classicMode.startBattle([ Species.SNORLAX ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + if (delay) { + game.move.select(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + } + + game.move.select(Moves.ENCORE); + + const turnOrder = delay + ? [ BattlerIndex.PLAYER, BattlerIndex.ENEMY ] + : [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]; + await game.setTurnOrder(turnOrder); + + await game.phaseInterceptor.to("BerryPhase", false); + expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.getTag(BattlerTagType.ENCORE)).toBeUndefined(); + }); + }); + + it("Pokemon under both Encore and Torment should alternate between Struggle and restricted move", async () => { + const turnOrder = [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]; + game.override.moveset([ Moves.ENCORE, Moves.TORMENT, Moves.SPLASH ]); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + game.move.select(Moves.ENCORE); + await game.setTurnOrder(turnOrder); + await game.phaseInterceptor.to("BerryPhase"); + expect(enemyPokemon?.getTag(BattlerTagType.ENCORE)).toBeDefined(); + + await game.toNextTurn(); + game.move.select(Moves.TORMENT); + await game.setTurnOrder(turnOrder); + await game.phaseInterceptor.to("BerryPhase"); + expect(enemyPokemon?.getTag(BattlerTagType.TORMENT)).toBeDefined(); + + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.setTurnOrder(turnOrder); + await game.phaseInterceptor.to("BerryPhase"); + const lastMove = enemyPokemon?.getLastXMoves()[0]; + expect(lastMove?.move).toBe(Moves.STRUGGLE); + }); +}); diff --git a/src/test/moves/endure.test.ts b/src/test/moves/endure.test.ts new file mode 100644 index 00000000000..bde5a26f68e --- /dev/null +++ b/src/test/moves/endure.test.ts @@ -0,0 +1,65 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Endure", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.THUNDER, Moves.BULLET_SEED, Moves.TOXIC ]) + .ability(Abilities.SKILL_LINK) + .startingLevel(100) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.NO_GUARD) + .enemyMoveset(Moves.ENDURE); + }); + + it("should let the pokemon survive with 1 HP", async () => { + await game.classicMode.startBattle([ Species.ARCEUS ]); + + game.move.select(Moves.THUNDER); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.hp).toBe(1); + }); + + it("should let the pokemon survive with 1 HP when hit with a multihit move", async () => { + await game.classicMode.startBattle([ Species.ARCEUS ]); + + game.move.select(Moves.BULLET_SEED); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.hp).toBe(1); + }); + + it("shouldn't prevent fainting from indirect damage", async () => { + game.override.enemyLevel(100); + await game.classicMode.startBattle([ Species.ARCEUS ]); + + const enemy = game.scene.getEnemyPokemon()!; + enemy.hp = 2; + + game.move.select(Moves.TOXIC); + await game.phaseInterceptor.to("VictoryPhase"); + + expect(enemy.isFainted()).toBe(true); + }); +}); diff --git a/src/test/moves/fissure.test.ts b/src/test/moves/fissure.test.ts index 12f075f1b55..15dabb971cc 100644 --- a/src/test/moves/fissure.test.ts +++ b/src/test/moves/fissure.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { Species } from "#app/enums/species"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -56,7 +56,7 @@ describe("Moves - Fissure", () => { game.override.enemyAbility(Abilities.FUR_COAT); game.move.select(Moves.FISSURE); - await game.phaseInterceptor.to(DamagePhase, true); + await game.phaseInterceptor.to(DamageAnimPhase, true); expect(enemyPokemon.isFainted()).toBe(true); }); diff --git a/src/test/moves/flower_shield.test.ts b/src/test/moves/flower_shield.test.ts index 1a5cd326fd8..4c03df5212b 100644 --- a/src/test/moves/flower_shield.test.ts +++ b/src/test/moves/flower_shield.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { SemiInvulnerableTag } from "#app/data/battler-tags"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Biome } from "#app/enums/biome"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/forests_curse.test.ts b/src/test/moves/forests_curse.test.ts index c49bdab5255..010b00599a5 100644 --- a/src/test/moves/forests_curse.test.ts +++ b/src/test/moves/forests_curse.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/freeze_dry.test.ts b/src/test/moves/freeze_dry.test.ts index c09d12fa597..9206a103a35 100644 --- a/src/test/moves/freeze_dry.test.ts +++ b/src/test/moves/freeze_dry.test.ts @@ -2,6 +2,8 @@ import { BattlerIndex } from "#app/battle"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; +import { Type } from "#enums/type"; +import { Challenges } from "#enums/challenges"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; @@ -28,7 +30,7 @@ describe("Moves - Freeze-Dry", () => { .enemyMoveset(Moves.SPLASH) .starterSpecies(Species.FEEBAS) .ability(Abilities.BALL_FETCH) - .moveset([ Moves.FREEZE_DRY ]); + .moveset([ Moves.FREEZE_DRY, Moves.FORESTS_CURSE, Moves.SOAK ]); }); it("should deal 2x damage to pure water types", async () => { @@ -72,8 +74,97 @@ describe("Moves - Freeze-Dry", () => { expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1); }); - // enable if this is ever fixed (lol) - it.todo("should deal 2x damage to water types under Normalize", async () => { + /** + * Freeze drys forced super effectiveness should overwrite wonder guard + */ + it("should deal 2x dmg against soaked wonder guard target", async () => { + game.override + .enemySpecies(Species.SHEDINJA) + .enemyMoveset(Moves.SPLASH) + .starterSpecies(Species.MAGIKARP) + .moveset([ Moves.SOAK, Moves.FREEZE_DRY ]); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.SOAK); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + game.move.select(Moves.FREEZE_DRY); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + }); + + it("should deal 8x damage to water/ground/grass type under Forest's Curse", async () => { + game.override.enemySpecies(Species.QUAGSIRE); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FORESTS_CURSE); + await game.toNextTurn(); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(8); + }); + + it("should deal 2x damage to steel type terastallized into water", async () => { + game.override.enemySpecies(Species.SKARMORY) + .enemyHeldItems([{ name: "TERA_SHARD", type: Type.WATER }]); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); + }); + + it("should deal 0.5x damage to water type terastallized into fire", async () => { + game.override.enemySpecies(Species.PELIPPER) + .enemyHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5); + }); + + it("should deal 0.5x damage to water type Terapagos with Tera Shell", async () => { + game.override.enemySpecies(Species.TERAPAGOS) + .enemyAbility(Abilities.TERA_SHELL); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.SOAK); + await game.toNextTurn(); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5); + }); + + it("should deal 2x damage to water type under Normalize", async () => { game.override.ability(Abilities.NORMALIZE); await game.classicMode.startBattle(); @@ -87,8 +178,39 @@ describe("Moves - Freeze-Dry", () => { expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); }); - // enable once Electrify is implemented (and the interaction is fixed, as above) - it.todo("should deal 2x damage to water types under Electrify", async () => { + it("should deal 0.25x damage to rock/steel type under Normalize", async () => { + game.override + .ability(Abilities.NORMALIZE) + .enemySpecies(Species.SHIELDON); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25); + }); + + it("should deal 0x damage to water/ghost type under Normalize", async () => { + game.override + .ability(Abilities.NORMALIZE) + .enemySpecies(Species.JELLICENT); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0); + }); + + it("should deal 2x damage to water type under Electrify", async () => { game.override.enemyMoveset([ Moves.ELECTRIFY ]); await game.classicMode.startBattle(); @@ -101,4 +223,128 @@ describe("Moves - Freeze-Dry", () => { expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2); }); + + it("should deal 4x damage to water/flying type under Electrify", async () => { + game.override + .enemyMoveset([ Moves.ELECTRIFY ]) + .enemySpecies(Species.GYARADOS); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4); + }); + + it("should deal 0x damage to water/ground type under Electrify", async () => { + game.override + .enemyMoveset([ Moves.ELECTRIFY ]) + .enemySpecies(Species.BARBOACH); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0); + }); + + it("should deal 0.25x damage to Grass/Dragon type under Electrify", async () => { + game.override + .enemyMoveset([ Moves.ELECTRIFY ]) + .enemySpecies(Species.FLAPPLE); + await game.classicMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25); + }); + + it("should deal 2x damage to Water type during inverse battle", async () => { + game.override + .moveset([ Moves.FREEZE_DRY ]) + .enemySpecies(Species.MAGIKARP); + game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); + + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); + }); + + it("should deal 2x damage to Water type during inverse battle under Normalize", async () => { + game.override + .moveset([ Moves.FREEZE_DRY ]) + .ability(Abilities.NORMALIZE) + .enemySpecies(Species.MAGIKARP); + game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); + }); + + it("should deal 2x damage to Water type during inverse battle under Electrify", async () => { + game.override + .moveset([ Moves.FREEZE_DRY ]) + .enemySpecies(Species.MAGIKARP) + .enemyMoveset([ Moves.ELECTRIFY ]); + game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2); + }); + + it("should deal 1x damage to water/flying type during inverse battle under Electrify", async () => { + game.override + .enemyMoveset([ Moves.ELECTRIFY ]) + .enemySpecies(Species.GYARADOS); + + game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1); + + await game.challengeMode.startBattle(); + + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(enemy, "getMoveEffectiveness"); + + game.move.select(Moves.FREEZE_DRY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1); + }); }); diff --git a/src/test/moves/fusion_flare.test.ts b/src/test/moves/fusion_flare.test.ts index 162cefcfb1e..75641c04d02 100644 --- a/src/test/moves/fusion_flare.test.ts +++ b/src/test/moves/fusion_flare.test.ts @@ -1,7 +1,7 @@ -import { StatusEffect } from "#app/data/status-effect"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/fusion_flare_bolt.test.ts b/src/test/moves/fusion_flare_bolt.test.ts index 1bcd0514357..dbd4479dff8 100644 --- a/src/test/moves/fusion_flare_bolt.test.ts +++ b/src/test/moves/fusion_flare_bolt.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -58,12 +58,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -81,12 +81,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -104,7 +104,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEndPhase); @@ -114,7 +114,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -133,7 +133,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEndPhase); @@ -142,7 +142,7 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); }, 20000); @@ -160,12 +160,12 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -209,22 +209,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); @@ -268,22 +268,22 @@ describe("Moves - Fusion Flare and Fusion Bolt", () => { await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(100); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionBolt.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionBolt.calculateBattlePower).toHaveLastReturnedWith(200); await game.phaseInterceptor.to(MoveEffectPhase, false); expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(fusionFlare.id); - await game.phaseInterceptor.to(DamagePhase, false); + await game.phaseInterceptor.to(DamageAnimPhase, false); expect(fusionFlare.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); }); diff --git a/src/test/moves/future_sight.test.ts b/src/test/moves/future_sight.test.ts new file mode 100644 index 00000000000..d0110a87202 --- /dev/null +++ b/src/test/moves/future_sight.test.ts @@ -0,0 +1,45 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Future Sight", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .startingLevel(50) + .moveset([ Moves.FUTURE_SIGHT, Moves.SPLASH ]) + .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.STURDY) + .enemyMoveset(Moves.SPLASH); + }); + + it("hits 2 turns after use, ignores user switch out", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + game.move.select(Moves.FUTURE_SIGHT); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.isFullHp()).toBe(false); + }); +}); diff --git a/src/test/moves/gastro_acid.test.ts b/src/test/moves/gastro_acid.test.ts index fdd75b90b13..ec9246c855c 100644 --- a/src/test/moves/gastro_acid.test.ts +++ b/src/test/moves/gastro_acid.test.ts @@ -62,7 +62,7 @@ describe("Moves - Gastro Acid", () => { }); it("fails if used on an enemy with an already-suppressed ability", async () => { - game.override.battleType(null); + game.override.battleType("single"); await game.startBattle(); diff --git a/src/test/moves/glaive_rush.test.ts b/src/test/moves/glaive_rush.test.ts index b36c3e20c7a..9cfbfdd8727 100644 --- a/src/test/moves/glaive_rush.test.ts +++ b/src/test/moves/glaive_rush.test.ts @@ -41,11 +41,11 @@ describe("Moves - Glaive Rush", () => { enemy.hp = 1000; game.move.select(Moves.SHADOW_SNEAK); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); const damageDealt = 1000 - enemy.hp; await game.phaseInterceptor.to("TurnEndPhase"); game.move.select(Moves.SHADOW_SNEAK); - await game.phaseInterceptor.to("DamagePhase"); + await game.phaseInterceptor.to("DamageAnimPhase"); expect(enemy.hp).toBeLessThanOrEqual(1001 - (damageDealt * 3)); }); diff --git a/src/test/moves/grudge.test.ts b/src/test/moves/grudge.test.ts new file mode 100644 index 00000000000..340808929ab --- /dev/null +++ b/src/test/moves/grudge.test.ts @@ -0,0 +1,90 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { BattlerIndex } from "#app/battle"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Grudge", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.EMBER, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHEDINJA) + .enemyAbility(Abilities.WONDER_GUARD) + .enemyMoveset([ Moves.GRUDGE, Moves.SPLASH ]); + }); + + it("should reduce the PP of the Pokemon's move to 0 when the user has fainted", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const playerPokemon = game.scene.getPlayerPokemon(); + game.move.select(Moves.EMBER); + await game.forceEnemyMove(Moves.GRUDGE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + const playerMove = playerPokemon?.getMoveset().find(m => m?.moveId === Moves.EMBER); + + expect(playerMove?.getPpRatio()).toBe(0); + }); + + it("should remain in effect until the user's next move", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const playerPokemon = game.scene.getPlayerPokemon(); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.GRUDGE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + game.move.select(Moves.EMBER); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase"); + + const playerMove = playerPokemon?.getMoveset().find(m => m?.moveId === Moves.EMBER); + + expect(playerMove?.getPpRatio()).toBe(0); + }); + + it("should not reduce the opponent's PP if the user dies to weather/indirect damage", async () => { + // Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm + game.override + .moveset([ Moves.FALSE_SWIPE ]) + .startingLevel(100) + .ability(Abilities.SAND_STREAM) + .enemySpecies(Species.RATTATA); + await game.classicMode.startBattle([ Species.GEODUDE ]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(Moves.FALSE_SWIPE); + await game.forceEnemyMove(Moves.GRUDGE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + + const playerMove = playerPokemon?.getMoveset().find(m => m?.moveId === Moves.FALSE_SWIPE); + expect(playerMove?.getPpRatio()).toBeGreaterThan(0); + }); +}); diff --git a/src/test/moves/heal_block.test.ts b/src/test/moves/heal_block.test.ts index 252533215a8..25f2076ff3e 100644 --- a/src/test/moves/heal_block.test.ts +++ b/src/test/moves/heal_block.test.ts @@ -1,12 +1,12 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; -import { WeatherType } from "#app/data/weather"; import GameManager from "#app/test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { WeatherType } from "#enums/weather-type"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/instruct.test.ts b/src/test/moves/instruct.test.ts new file mode 100644 index 00000000000..0e227ef6a3f --- /dev/null +++ b/src/test/moves/instruct.test.ts @@ -0,0 +1,315 @@ +import { BattlerIndex } from "#app/battle"; +import type Pokemon from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Instruct", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + function instructSuccess(pokemon: Pokemon, move: Moves): void { + expect(pokemon.getLastXMoves(-1)[0].move).toBe(move); + expect(pokemon.getLastXMoves(-1)[1].move).toBe(pokemon.getLastXMoves()[0].move); + expect(pokemon.getMoveset().find(m => m?.moveId === move)?.ppUsed).toBe(2); + } + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.NO_GUARD) + .enemyLevel(100) + .startingLevel(100) + .ability(Abilities.BALL_FETCH) + .moveset([ Moves.INSTRUCT, Moves.SONIC_BOOM, Moves.SPLASH, Moves.TORMENT ]) + .disableCrits(); + }); + + it("should repeat enemy's attack move when moving last", async () => { + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + const enemy = game.scene.getEnemyPokemon()!; + game.move.changeMoveset(enemy, Moves.SONIC_BOOM); + + game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.SONIC_BOOM, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getPlayerPokemon()?.getInverseHp()).toBe(40); + instructSuccess(enemy, Moves.SONIC_BOOM); + }); + + it("should repeat enemy's move through substitute", async () => { + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + const enemy = game.scene.getEnemyPokemon()!; + game.move.changeMoveset(enemy, [ Moves.SONIC_BOOM, Moves.SUBSTITUTE ]); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SUBSTITUTE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.SONIC_BOOM); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getPlayerPokemon()?.getInverseHp()).toBe(40); + instructSuccess(game.scene.getEnemyPokemon()!, Moves.SONIC_BOOM); + }); + + it("should repeat ally's attack on enemy", async () => { + game.override + .battleType("double") + .moveset([]); + await game.classicMode.startBattle([ Species.AMOONGUSS, Species.SHUCKLE ]); + + const [ amoonguss, shuckle ] = game.scene.getPlayerField(); + game.move.changeMoveset(amoonguss, Moves.INSTRUCT); + game.move.changeMoveset(shuckle, Moves.SONIC_BOOM); + + game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2); + game.move.select(Moves.SONIC_BOOM, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getEnemyField()[0].getInverseHp()).toBe(40); + instructSuccess(shuckle, Moves.SONIC_BOOM); + }); + + // TODO: Enable test case once gigaton hammer (and blood moon) is fixed + it.todo("should repeat enemy's Gigaton Hammer", async () => { + game.override + .enemyLevel(5); + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + const enemy = game.scene.getEnemyPokemon()!; + game.move.changeMoveset(enemy, Moves.GIGATON_HAMMER); + + game.move.select(Moves.INSTRUCT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + instructSuccess(enemy, Moves.GIGATON_HAMMER); + }); + + it("should respect enemy's status condition", async () => { + game.override + .moveset([ Moves.THUNDER_WAVE, Moves.INSTRUCT ]) + .enemyMoveset([ Moves.SPLASH, Moves.SONIC_BOOM ]); + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + game.move.select(Moves.THUNDER_WAVE); + await game.forceEnemyMove(Moves.SONIC_BOOM); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.move.forceStatusActivation(true); + await game.phaseInterceptor.to("MovePhase"); + await game.move.forceStatusActivation(false); + await game.phaseInterceptor.to("TurnEndPhase", false); + + const moveHistory = game.scene.getEnemyPokemon()!.getMoveHistory(); + expect(moveHistory.length).toBe(3); + expect(moveHistory[0].move).toBe(Moves.SONIC_BOOM); + expect(moveHistory[1].move).toBe(Moves.NONE); + expect(moveHistory[2].move).toBe(Moves.SONIC_BOOM); + }); + + it("should not repeat enemy's out of pp move", async () => { + game.override.enemySpecies(Species.UNOWN); + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + game.move.changeMoveset(enemyPokemon, Moves.HIDDEN_POWER); + const moveUsed = enemyPokemon.moveset.find(m => m?.moveId === Moves.HIDDEN_POWER)!; + moveUsed.ppUsed = moveUsed.getMovePp() - 1; + + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.HIDDEN_POWER); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + const playerMove = game.scene.getPlayerPokemon()!.getLastXMoves()!; + expect(playerMove[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.getMoveHistory().length).toBe(1); + }); + + it("should fail if no move has yet been used by target", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should attempt to call enemy's disabled move, but move use itself should fail", async () => { + game.override + .moveset([ Moves.INSTRUCT, Moves.DISABLE ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.AMOONGUSS, Species.DROWZEE ]); + + const [ enemy1, enemy2 ] = game.scene.getEnemyField(); + game.move.changeMoveset(enemy1, Moves.SONIC_BOOM); + game.move.changeMoveset(enemy2, Moves.SPLASH); + + game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.DISABLE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.SONIC_BOOM, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getPlayerField()[0].getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + const enemyMove = game.scene.getEnemyPokemon()!.getLastXMoves()[0]; + expect(enemyMove.result).toBe(MoveResult.FAIL); + expect(game.scene.getEnemyPokemon()!.getMoveset().find(m => m?.moveId === Moves.SONIC_BOOM)?.ppUsed).toBe(1); + + }); + + it("should not repeat enemy's move through protect", async () => { + await game.classicMode.startBattle([ Species.AMOONGUSS ]); + + const MoveToUse = Moves.PROTECT; + const enemyPokemon = game.scene.getEnemyPokemon()!; + game.move.changeMoveset(enemyPokemon, MoveToUse); + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.PROTECT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(enemyPokemon.getLastXMoves(-1)[0].move).toBe(Moves.PROTECT); + expect(enemyPokemon.getLastXMoves(-1)[1]).toBeUndefined(); // undefined because protect failed + expect(enemyPokemon.getMoveset().find(m => m?.moveId === Moves.PROTECT)?.ppUsed).toBe(1); + }); + + it("should not repeat enemy's charging move", async () => { + game.override + .enemyMoveset([ Moves.SONIC_BOOM, Moves.HYPER_BEAM ]) + .enemyLevel(5); + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + enemyPokemon.battleSummonData.moveHistory = [{ move: Moves.SONIC_BOOM, targets: [ BattlerIndex.PLAYER ], result: MoveResult.SUCCESS, virtual: false }]; + + game.move.select(Moves.INSTRUCT); + await game.forceEnemyMove(Moves.HYPER_BEAM); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + + game.move.select(Moves.INSTRUCT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(player.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should not repeat dance move not known by target", async () => { + game.override + .battleType("double") + .moveset([ Moves.INSTRUCT, Moves.FIERY_DANCE ]) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.DANCER); + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + + game.move.select(Moves.INSTRUCT, BattlerIndex.PLAYER, BattlerIndex.ENEMY); + game.move.select(Moves.FIERY_DANCE, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER_2, BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("TurnEndPhase", false); + + expect(game.scene.getPlayerField()[0].getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should cause multi-hit moves to hit the appropriate number of times in singles", async () => { + game.override + .enemyAbility(Abilities.SKILL_LINK) + .enemyMoveset(Moves.BULLET_SEED); + await game.classicMode.startBattle([ Species.BULBASAUR ]); + + const player = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + game.move.select(Moves.INSTRUCT); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(player.turnData.attacksReceived.length).toBe(10); + + await game.toNextTurn(); + game.move.select(Moves.INSTRUCT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(player.turnData.attacksReceived.length).toBe(10); + }); + + it("should cause multi-hit moves to hit the appropriate number of times in doubles", async () => { + game.override + .battleType("double") + .enemyAbility(Abilities.SKILL_LINK) + .enemyMoveset([ Moves.BULLET_SEED, Moves.SPLASH ]) + .enemyLevel(5); + await game.classicMode.startBattle([ Species.BULBASAUR, Species.IVYSAUR ]); + + const [ , ivysaur ] = game.scene.getPlayerField(); + + game.move.select(Moves.SPLASH); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + game.move.select(Moves.INSTRUCT, 0, BattlerIndex.ENEMY); + game.move.select(Moves.INSTRUCT, 1, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(ivysaur.turnData.attacksReceived.length).toBe(15); + + await game.toNextTurn(); + game.move.select(Moves.INSTRUCT, 0, BattlerIndex.ENEMY); + game.move.select(Moves.INSTRUCT, 1, BattlerIndex.ENEMY); + await game.forceEnemyMove(Moves.BULLET_SEED, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(ivysaur.turnData.attacksReceived.length).toBe(15); + }); +}); diff --git a/src/test/moves/lunar_dance.test.ts b/src/test/moves/lunar_dance.test.ts new file mode 100644 index 00000000000..603247298ac --- /dev/null +++ b/src/test/moves/lunar_dance.test.ts @@ -0,0 +1,77 @@ +import { StatusEffect } from "#app/enums/status-effect"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; + +describe("Moves - Lunar Dance", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .statusEffect(StatusEffect.BURN) + .battleType("double") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should full restore HP, PP and status of switched in pokemon, then fail second use because no remaining backup pokemon in party", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR, Species.ODDISH, Species.RATTATA ]); + + const [ bulbasaur, oddish, rattata ] = game.scene.getPlayerParty(); + game.move.changeMoveset(bulbasaur, [ Moves.LUNAR_DANCE, Moves.SPLASH ]); + game.move.changeMoveset(oddish, [ Moves.LUNAR_DANCE, Moves.SPLASH ]); + game.move.changeMoveset(rattata, [ Moves.LUNAR_DANCE, Moves.SPLASH ]); + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + await game.phaseInterceptor.to(CommandPhase); + await game.toNextTurn(); + + // Bulbasaur should still be burned and have used a PP for splash and not at max hp + expect(bulbasaur.status?.effect).toBe(StatusEffect.BURN); + expect(bulbasaur.moveset[1]?.ppUsed).toBe(1); + expect(bulbasaur.hp).toBeLessThan(bulbasaur.getMaxHp()); + + // Switch out Bulbasaur for Rattata so we can swtich bulbasaur back in with lunar dance + game.doSwitchPokemon(2); + game.move.select(Moves.SPLASH, 1); + await game.phaseInterceptor.to(CommandPhase); + await game.toNextTurn(); + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.LUNAR_DANCE); + game.doSelectPartyPokemon(2); + await game.phaseInterceptor.to("SwitchPhase", false); + await game.toNextTurn(); + + // Bulbasaur should NOT have any status and have full PP for splash and be at max hp + expect(bulbasaur.status?.effect).toBeUndefined(); + expect(bulbasaur.moveset[1]?.ppUsed).toBe(0); + expect(bulbasaur.isFullHp()).toBe(true); + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.LUNAR_DANCE); + await game.phaseInterceptor.to(CommandPhase); + await game.toNextTurn(); + + // Using Lunar dance again should fail because nothing in party and rattata should be alive + expect(rattata.status?.effect).toBe(StatusEffect.BURN); + expect(rattata.hp).toBeLessThan(rattata.getMaxHp()); + }); +}); diff --git a/src/test/moves/moongeist_beam.test.ts b/src/test/moves/moongeist_beam.test.ts new file mode 100644 index 00000000000..216eee482fb --- /dev/null +++ b/src/test/moves/moongeist_beam.test.ts @@ -0,0 +1,59 @@ +import { allMoves, RandomMoveAttr } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Moongeist Beam", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MOONGEIST_BEAM, Moves.METRONOME ]) + .ability(Abilities.BALL_FETCH) + .startingLevel(200) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.STURDY) + .enemyMoveset(Moves.SPLASH); + }); + + // Also covers Photon Geyser and Sunsteel Strike + it("should ignore enemy abilities", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + + const enemy = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.MOONGEIST_BEAM); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemy.isFainted()).toBe(true); + }); + + // Also covers Photon Geyser and Sunsteel Strike + it("should not ignore enemy abilities when called by another move, such as metronome", async () => { + await game.classicMode.startBattle([ Species.MILOTIC ]); + vi.spyOn(allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0], "getMoveOverride").mockReturnValue(Moves.MOONGEIST_BEAM); + + game.move.select(Moves.METRONOME); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.isFainted()).toBe(false); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].move).toBe(Moves.MOONGEIST_BEAM); + }); +}); diff --git a/src/test/moves/nightmare.test.ts b/src/test/moves/nightmare.test.ts index f4c485ff1b4..850b0793b1e 100644 --- a/src/test/moves/nightmare.test.ts +++ b/src/test/moves/nightmare.test.ts @@ -1,7 +1,7 @@ -import { StatusEffect } from "#app/data/status-effect"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/octolock.test.ts b/src/test/moves/octolock.test.ts index d80b71a51e1..6ca96eeb464 100644 --- a/src/test/moves/octolock.test.ts +++ b/src/test/moves/octolock.test.ts @@ -1,11 +1,8 @@ -import { Stat } from "#enums/stat"; import { TrappedTag } from "#app/data/battler-tags"; -import { CommandPhase } from "#app/phases/command-phase"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; -import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -27,12 +24,13 @@ describe("Moves - Octolock", () => { beforeEach(() => { game = new GameManager(phaserGame); - game.override.battleType("single") - .enemySpecies(Species.RATTATA) + game.override + .battleType("single") + .enemySpecies(Species.MAGIKARP) .enemyMoveset(Moves.SPLASH) .enemyAbility(Abilities.BALL_FETCH) .startingLevel(2000) - .moveset([ Moves.OCTOLOCK, Moves.SPLASH ]) + .moveset([ Moves.OCTOLOCK, Moves.SPLASH, Moves.TRICK_OR_TREAT ]) .ability(Abilities.BALL_FETCH); }); @@ -43,16 +41,15 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); // take a second turn to make sure stat changes occur again - await game.phaseInterceptor.to(CommandPhase); game.move.select(Moves.SPLASH); + await game.toNextTurn(); - await game.phaseInterceptor.to(TurnInitPhase); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-2); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-2); }); @@ -65,7 +62,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1); @@ -79,7 +76,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -93,7 +90,7 @@ describe("Moves - Octolock", () => { // use Octolock and advance to init phase of next turn to check for stat changes game.move.select(Moves.OCTOLOCK); - await game.phaseInterceptor.to(TurnInitPhase); + await game.toNextTurn(); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -110,7 +107,44 @@ describe("Moves - Octolock", () => { game.move.select(Moves.OCTOLOCK); // after Octolock - enemy should be trapped - await game.phaseInterceptor.to(MoveEndPhase); + await game.phaseInterceptor.to("MoveEndPhase"); expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeDefined(); }); + + it("does not work on ghost type pokemon", async () => { + game.override.enemyMoveset(Moves.OCTOLOCK); + await game.classicMode.startBattle([ Species.GASTLY ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + + // before Octolock - player should not be trapped + expect(playerPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + // after Octolock - player should still not be trapped, and no stat loss + expect(playerPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(0); + }); + + it("does not work on pokemon with added ghost type via Trick-or-Treat", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemy = game.scene.getEnemyPokemon()!; + + // before Octolock - pokemon should not be trapped + expect(enemy.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + + game.move.select(Moves.TRICK_OR_TREAT); + await game.toNextTurn(); + game.move.select(Moves.OCTOLOCK); + await game.toNextTurn(); + + // after Octolock - pokemon should still not be trapped, and no stat loss + expect(enemy.findTag(t => t instanceof TrappedTag)).toBeUndefined(); + expect(enemy.getStatStage(Stat.DEF)).toBe(0); + expect(enemy.getStatStage(Stat.SPDEF)).toBe(0); + }); }); diff --git a/src/test/moves/order_up.test.ts b/src/test/moves/order_up.test.ts new file mode 100644 index 00000000000..d0b52dc1a9d --- /dev/null +++ b/src/test/moves/order_up.test.ts @@ -0,0 +1,85 @@ +import { BattlerIndex } from "#app/battle"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import { EffectiveStat, Stat } from "#enums/stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Order Up", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.ORDER_UP) + .ability(Abilities.COMMANDER) + .battleType("double") + .disableCrits() + .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) + .enemyLevel(100); + + vi.spyOn(game.scene, "triggerPokemonBattleAnim").mockReturnValue(true); + }); + + it.each([ + { formIndex: 0, formName: "Curly", stat: Stat.ATK, statName: "Attack" }, + { formIndex: 1, formName: "Droopy", stat: Stat.DEF, statName: "Defense" }, + { formIndex: 2, formName: "Stretchy", stat: Stat.SPD, statName: "Speed" } + ])("should raise the user's $statName when the user is commanded by a $formName Tatsugiri", async ({ formIndex, stat }) => { + game.override.starterForms({ [Species.TATSUGIRI]: formIndex }); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.ORDER_UP, 1, BattlerIndex.ENEMY); + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.phaseInterceptor.to("BerryPhase", false); + + const affectedStats: EffectiveStat[] = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; + affectedStats.forEach(st => expect(dondozo.getStatStage(st)).toBe(st === stat ? 3 : 2)); + }); + + it("should be boosted by Sheer Force while still applying a stat boost", async () => { + game.override + .passiveAbility(Abilities.SHEER_FORCE) + .starterForms({ [Species.TATSUGIRI]: 0 }); + + await game.classicMode.startBattle([ Species.TATSUGIRI, Species.DONDOZO ]); + + const [ tatsugiri, dondozo ] = game.scene.getPlayerField(); + + expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY); + expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined(); + + game.move.select(Moves.ORDER_UP, 1, BattlerIndex.ENEMY); + expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy(); + + await game.phaseInterceptor.to("BerryPhase", false); + + expect(dondozo.battleData.abilitiesApplied.includes(Abilities.SHEER_FORCE)).toBeTruthy(); + expect(dondozo.getStatStage(Stat.ATK)).toBe(3); + }); +}); diff --git a/src/test/moves/plasma_fists.test.ts b/src/test/moves/plasma_fists.test.ts index 24210cd47fa..4075c1ab988 100644 --- a/src/test/moves/plasma_fists.test.ts +++ b/src/test/moves/plasma_fists.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/pledge_moves.test.ts b/src/test/moves/pledge_moves.test.ts index 93fcf57cc60..64d586e7ba4 100644 --- a/src/test/moves/pledge_moves.test.ts +++ b/src/test/moves/pledge_moves.test.ts @@ -2,7 +2,7 @@ import { BattlerIndex } from "#app/battle"; import { allAbilities } from "#app/data/ability"; import { ArenaTagSide } from "#app/data/arena-tag"; import { allMoves, FlinchAttr } from "#app/data/move"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Stat } from "#enums/stat"; import { toDmgValue } from "#app/utils"; diff --git a/src/test/moves/powder.test.ts b/src/test/moves/powder.test.ts new file mode 100644 index 00000000000..a1db2bced3a --- /dev/null +++ b/src/test/moves/powder.test.ts @@ -0,0 +1,318 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#test/utils/gameManager"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { BerryPhase } from "#app/phases/berry-phase"; +import { MoveResult, PokemonMove } from "#app/field/pokemon"; +import { Type } from "#enums/type"; +import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; + +describe("Moves - Powder", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single"); + + game.override + .enemySpecies(Species.SNORLAX) + .enemyLevel(100) + .enemyMoveset(Moves.EMBER) + .enemyAbility(Abilities.INSOMNIA) + .startingLevel(100) + .moveset([ Moves.POWDER, Moves.SPLASH, Moves.FIERY_DANCE, Moves.ROAR ]); + }); + + it( + "should cancel the target's Fire-type move, damage the target, and still consume the target's PP", + async () => { + // Cannot use enemy moveset override for this test, since it interferes with checking PP + game.override.enemyMoveset([]); + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + enemyPokemon.moveset = [ new PokemonMove(Moves.EMBER) ]; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + expect(enemyPokemon.moveset[0]!.ppUsed).toBe(1); + + await game.toNextTurn(); + + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + expect(enemyPokemon.moveset[0]!.ppUsed).toBe(2); + }); + + it( + "should have no effect against Grass-type Pokemon", + async () => { + game.override.enemySpecies(Species.AMOONGUSS); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }); + + it( + "should have no effect against Pokemon with Overcoat", + async () => { + game.override.enemyAbility(Abilities.OVERCOAT); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }); + + it( + "should not damage the target if the target has Magic Guard", + async () => { + game.override.enemyAbility(Abilities.MAGIC_GUARD); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }); + + it( + "should not damage the target if Primordial Sea is active", + async () => { + game.override.enemyAbility(Abilities.PRIMORDIAL_SEA); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }); + + it( + "should not prevent the target from thawing out with Flame Wheel", + async () => { + game.override + .enemyMoveset(Array(4).fill(Moves.FLAME_WHEEL)) + .enemyStatusEffect(StatusEffect.FREEZE); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.FREEZE); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + } + ); + + it( + "should not allow a target with Protean to change to Fire type", + async () => { + game.override.enemyAbility(Abilities.PROTEAN); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); + expect(enemyPokemon.summonData?.types).not.toBe(Type.FIRE); + }); + + it( + "should cancel Fire-type moves generated by the target's Dancer ability", + async () => { + game.override + .battleType("double") + .enemySpecies(Species.BLASTOISE) + .enemyAbility(Abilities.DANCER); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + // Turn 1: Roar away 1 opponent + game.move.select(Moves.ROAR, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + await game.toNextTurn(); // Requires game.toNextTurn() twice due to double battle + + // Turn 2: Enemy should activate Powder twice: From using Ember, and from copying Fiery Dance via Dancer + playerPokemon.hp = playerPokemon.getMaxHp(); + game.move.select(Moves.FIERY_DANCE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.POWDER, 1, BattlerIndex.ENEMY); + + await game.phaseInterceptor.to(MoveEffectPhase); + const enemyStartingHp = enemyPokemon.hp; + + await game.phaseInterceptor.to(BerryPhase, false); + + + // player should not take damage + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp()); + // enemy should have taken damage from player's Fiery Dance + 2 Powder procs + expect(enemyPokemon.hp).toBe(enemyStartingHp - playerPokemon.turnData.totalDamageDealt - 2 * Math.floor(enemyPokemon.getMaxHp() / 4)); + }); + + it( + "should cancel Fiery Dance, then prevent it from triggering Dancer", + async () => { + game.override.ability(Abilities.DANCER) + .enemyMoveset(Moves.FIERY_DANCE); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + expect(playerPokemon.getLastXMoves()[0].move).toBe(Moves.POWDER); + }); + + it( + "should cancel Revelation Dance if it becomes a Fire-type move", + async () => { + game.override + .enemySpecies(Species.CHARIZARD) + .enemyMoveset(Array(4).fill(Moves.REVELATION_DANCE)); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }); + + it( + "should cancel Shell Trap and damage the target, even if the move would fail", + async () => { + game.override.enemyMoveset(Array(4).fill(Moves.SHELL_TRAP)); + + await game.classicMode.startBattle([ Species.CHARIZARD ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }); + + it( + "should cancel Grass Pledge if used after ally's Fire Pledge", + async () => { + game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.GRASS_PLEDGE ]) + .battleType("double"); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.GRASS_PLEDGE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }); + + it( + "should cancel Fire Pledge if used before ally's Water Pledge", + async () => { + game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE ]) + .battleType("double"); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(enemyPokemon.hp).toBe(Math.ceil(3 * enemyPokemon.getMaxHp() / 4)); + }); + + it( + "should NOT cancel Fire Pledge if used after ally's Water Pledge", + async () => { + game.override.enemyMoveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE ]) + .battleType("double"); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.CHARIZARD ]); + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.POWDER, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.FIRE_PLEDGE, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.WATER_PLEDGE, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2, BattlerIndex.ENEMY ]); + + await game.phaseInterceptor.to(BerryPhase, false); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); + }); +}); diff --git a/src/test/moves/psycho_shift.test.ts b/src/test/moves/psycho_shift.test.ts new file mode 100644 index 00000000000..448a8c99ef0 --- /dev/null +++ b/src/test/moves/psycho_shift.test.ts @@ -0,0 +1,49 @@ +import { StatusEffect } from "#app/enums/status-effect"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Psycho Shift", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.PSYCHO_SHIFT ]) + .ability(Abilities.BALL_FETCH) + .statusEffect(StatusEffect.POISON) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyLevel(20) + .enemyAbility(Abilities.SYNCHRONIZE) + .enemyMoveset(Moves.SPLASH); + }); + + it("If Psycho Shift is used on a Pokémon with Synchronize, the user of Psycho Shift will already be afflicted with a status condition when Synchronize activates", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const playerPokemon = game.scene.getPlayerPokemon(); + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon?.status).toBeUndefined(); + + game.move.select(Moves.PSYCHO_SHIFT); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.status).toBeNull(); + expect(enemyPokemon?.status).toBeDefined(); + }); +}); diff --git a/src/test/moves/purify.test.ts b/src/test/moves/purify.test.ts index 3b07eafd75a..171f94a611a 100644 --- a/src/test/moves/purify.test.ts +++ b/src/test/moves/purify.test.ts @@ -1,9 +1,10 @@ import { BattlerIndex } from "#app/battle"; -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; diff --git a/src/test/moves/reflect_type.test.ts b/src/test/moves/reflect_type.test.ts index 0e47d4b00fc..50e0fc2fbe6 100644 --- a/src/test/moves/reflect_type.test.ts +++ b/src/test/moves/reflect_type.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/relic_song.test.ts b/src/test/moves/relic_song.test.ts index eb877b6054d..c09514850eb 100644 --- a/src/test/moves/relic_song.test.ts +++ b/src/test/moves/relic_song.test.ts @@ -1,4 +1,4 @@ -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Challenges } from "#app/enums/challenges"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/roost.test.ts b/src/test/moves/roost.test.ts index e595f879844..69301dc86cf 100644 --- a/src/test/moves/roost.test.ts +++ b/src/test/moves/roost.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; diff --git a/src/test/moves/scale_shot.test.ts b/src/test/moves/scale_shot.test.ts index e4d768fa13a..cbaa6611f3e 100644 --- a/src/test/moves/scale_shot.test.ts +++ b/src/test/moves/scale_shot.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; @@ -48,7 +48,7 @@ describe("Moves - Scale Shot", () => { await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); await game.phaseInterceptor.to(MoveEffectPhase); - await game.phaseInterceptor.to(DamagePhase); + await game.phaseInterceptor.to(DamageAnimPhase); //check that stats haven't changed after one or two hits have occurred await game.phaseInterceptor.to(MoveEffectPhase); diff --git a/src/test/moves/shell_side_arm.test.ts b/src/test/moves/shell_side_arm.test.ts index 9646d27f17e..41cbefb186b 100644 --- a/src/test/moves/shell_side_arm.test.ts +++ b/src/test/moves/shell_side_arm.test.ts @@ -26,7 +26,7 @@ describe("Moves - Shell Side Arm", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .moveset([ Moves.SHELL_SIDE_ARM ]) + .moveset([ Moves.SHELL_SIDE_ARM, Moves.SPLASH ]) .battleType("single") .startingLevel(100) .enemyLevel(100) @@ -69,6 +69,9 @@ describe("Moves - Shell Side Arm", () => { vi.spyOn(shellSideArmAttr, "apply"); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + game.move.select(Moves.SHELL_SIDE_ARM); await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); await game.phaseInterceptor.to("BerryPhase", false); diff --git a/src/test/moves/sketch.test.ts b/src/test/moves/sketch.test.ts index 2e3eb97a76c..f531f44ef0c 100644 --- a/src/test/moves/sketch.test.ts +++ b/src/test/moves/sketch.test.ts @@ -1,10 +1,13 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MoveResult } from "#app/field/pokemon"; +import { MoveResult, PokemonMove } from "#app/field/pokemon"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { StatusEffect } from "#app/enums/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; describe("Moves - Sketch", () => { let phaserGame: Phaser.Game; @@ -32,22 +35,64 @@ describe("Moves - Sketch", () => { }); it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => { - game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]); - await game.classicMode.startBattle([ Species.REGIELEKI ]); - const playerPokemon = game.scene.getPlayerPokemon(); + const playerPokemon = game.scene.getPlayerPokemon()!; + // can't use normal moveset override because we need to check moveset changes + playerPokemon.moveset = [ new PokemonMove(Moves.SKETCH), new PokemonMove(Moves.SKETCH) ]; game.move.select(Moves.SKETCH); await game.phaseInterceptor.to("TurnEndPhase"); - expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL); - const moveSlot0 = playerPokemon?.getMoveset()[0]; - expect(moveSlot0?.moveId).toBe(Moves.SKETCH); - expect(moveSlot0?.getPpRatio()).toBe(0); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + const moveSlot0 = playerPokemon.getMoveset()[0]!; + expect(moveSlot0.moveId).toBe(Moves.SKETCH); + expect(moveSlot0.getPpRatio()).toBe(0); await game.toNextTurn(); game.move.select(Moves.SKETCH); await game.phaseInterceptor.to("TurnEndPhase"); - expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); - // Can't verify if the player Pokemon's moveset was successfully changed because of overrides. + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(playerPokemon.moveset[0]?.moveId).toBe(Moves.SPLASH); + expect(playerPokemon.moveset[1]?.moveId).toBe(Moves.SKETCH); + }); + + it("Sketch should retrieve the most recent valid move from its target history", async () => { + game.override.enemyStatusEffect(StatusEffect.PARALYSIS); + await game.classicMode.startBattle([ Species.REGIELEKI ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyPokemon = game.scene.getEnemyPokemon()!; + playerPokemon.moveset = [ new PokemonMove(Moves.SKETCH), new PokemonMove(Moves.GROWL) ]; + + game.move.select(Moves.GROWL); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.move.forceStatusActivation(false); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + + await game.toNextTurn(); + game.move.select(Moves.SKETCH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.move.forceStatusActivation(true); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(playerPokemon.moveset[0]?.moveId).toBe(Moves.SPLASH); + expect(playerPokemon.moveset[1]?.moveId).toBe(Moves.GROWL); + }); + + it("should sketch moves that call other moves", async () => { + const randomMoveAttr = allMoves[Moves.METRONOME].findAttr(attr => attr instanceof RandomMoveAttr) as RandomMoveAttr; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.FALSE_SWIPE); + + game.override.enemyMoveset([ Moves.METRONOME ]); + await game.classicMode.startBattle([ Species.REGIELEKI ]); + const playerPokemon = game.scene.getPlayerPokemon()!; + playerPokemon.moveset = [ new PokemonMove(Moves.SKETCH) ]; + + // Opponent uses Metronome -> False Swipe, then player uses Sketch, which should sketch Metronome + game.move.select(Moves.SKETCH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(playerPokemon.moveset[0]?.moveId).toBe(Moves.METRONOME); + expect(playerPokemon.hp).toBeLessThan(playerPokemon.getMaxHp()); // Make sure opponent actually used False Swipe }); }); diff --git a/src/test/moves/substitute.test.ts b/src/test/moves/substitute.test.ts index 92f66c967c4..14ab4ab5054 100644 --- a/src/test/moves/substitute.test.ts +++ b/src/test/moves/substitute.test.ts @@ -2,7 +2,11 @@ import { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#app/data/arena-tag"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; -import { StatusEffect } from "#app/data/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { CommandPhase } from "#app/phases/command-phase"; +import GameManager from "#app/test/utils/gameManager"; +import { Command } from "#app/ui/command-ui-handler"; +import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -10,11 +14,7 @@ import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; -import { MoveResult } from "#app/field/pokemon"; -import { CommandPhase } from "#app/phases/command-phase"; -import GameManager from "#app/test/utils/gameManager"; -import { Command } from "#app/ui/command-ui-handler"; -import { Mode } from "#app/ui/ui"; +import { StatusEffect } from "#enums/status-effect"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; diff --git a/src/test/moves/tar_shot.test.ts b/src/test/moves/tar_shot.test.ts index 4734da366e4..5fb70abc19c 100644 --- a/src/test/moves/tar_shot.test.ts +++ b/src/test/moves/tar_shot.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { Stat } from "#app/enums/stat"; diff --git a/src/test/moves/tera_blast.test.ts b/src/test/moves/tera_blast.test.ts index 0ce8a552105..311ac0f0d0e 100644 --- a/src/test/moves/tera_blast.test.ts +++ b/src/test/moves/tera_blast.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { Stat } from "#enums/stat"; import { allMoves } from "#app/data/move"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#app/enums/abilities"; import { HitResult } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/tera_starstorm.test.ts b/src/test/moves/tera_starstorm.test.ts index c7b6b66c1ce..22dd5b3c4d1 100644 --- a/src/test/moves/tera_starstorm.test.ts +++ b/src/test/moves/tera_starstorm.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/thunder_wave.test.ts b/src/test/moves/thunder_wave.test.ts index 03e9ebb94f3..5551451e59b 100644 --- a/src/test/moves/thunder_wave.test.ts +++ b/src/test/moves/thunder_wave.test.ts @@ -1,8 +1,8 @@ -import { StatusEffect } from "#app/data/status-effect"; -import { Abilities } from "#app/enums/abilities"; import { EnemyPokemon } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index a5c63a2652f..bdd59ed0ac8 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -1,10 +1,10 @@ import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { StatusEffect } from "#app/data/status-effect"; import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/trick_or_treat.test.ts b/src/test/moves/trick_or_treat.test.ts index 7ecd00ed076..5c85cac05e2 100644 --- a/src/test/moves/trick_or_treat.test.ts +++ b/src/test/moves/trick_or_treat.test.ts @@ -1,7 +1,7 @@ import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/moves/upper_hand.test.ts b/src/test/moves/upper_hand.test.ts new file mode 100644 index 00000000000..f94197d3fbd --- /dev/null +++ b/src/test/moves/upper_hand.test.ts @@ -0,0 +1,103 @@ +import { BattlerIndex } from "#app/battle"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Upper Hand", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset(Moves.UPPER_HAND) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.QUICK_ATTACK) + .startingLevel(100) + .enemyLevel(100); + }); + + it("should flinch the opponent before they use a priority attack", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + const magikarp = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.UPPER_HAND); + await game.phaseInterceptor.to("BerryPhase"); + + expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(magikarp.isFullHp()).toBeFalsy(); + expect(feebas.isFullHp()).toBeTruthy(); + }); + + it.each([ + { descriptor: "non-priority attack", move: Moves.TACKLE }, + { descriptor: "status move", move: Moves.BABY_DOLL_EYES } + ])("should fail when the opponent selects a $descriptor", async ({ move }) => { + game.override.enemyMoveset(move); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.UPPER_HAND); + await game.phaseInterceptor.to("BerryPhase"); + + expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should flinch the opponent before they use an attack boosted by Gale Wings", async () => { + game.override + .enemyAbility(Abilities.GALE_WINGS) + .enemyMoveset(Moves.GUST); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + const magikarp = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.UPPER_HAND); + await game.phaseInterceptor.to("BerryPhase"); + + expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + expect(magikarp.isFullHp()).toBeFalsy(); + expect(feebas.isFullHp()).toBeTruthy(); + }); + + it("should fail if the target has already moved", async () => { + game.override + .enemyMoveset(Moves.FAKE_OUT) + .enemyAbility(Abilities.SHEER_FORCE); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + + game.move.select(Moves.UPPER_HAND); + + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(feebas.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + expect(feebas.isFullHp()).toBeFalsy(); + }); +}); diff --git a/src/test/moves/whirlwind.test.ts b/src/test/moves/whirlwind.test.ts index c16f38111f2..69232bee43a 100644 --- a/src/test/moves/whirlwind.test.ts +++ b/src/test/moves/whirlwind.test.ts @@ -1,11 +1,15 @@ -import { BattlerTagType } from "#app/enums/battler-tag-type"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Challenges } from "#enums/challenges"; +import { Type } from "#enums/type"; import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { Status } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; describe("Moves - Whirlwind", () => { let phaserGame: Phaser.Game; @@ -25,8 +29,9 @@ describe("Moves - Whirlwind", () => { game = new GameManager(phaserGame); game.override .battleType("single") + .moveset(Moves.SPLASH) .enemyAbility(Abilities.BALL_FETCH) - .enemyMoveset(Moves.WHIRLWIND) + .enemyMoveset([ Moves.SPLASH, Moves.WHIRLWIND ]) .enemySpecies(Species.PIDGEY); }); @@ -41,10 +46,114 @@ describe("Moves - Whirlwind", () => { const staraptor = game.scene.getPlayerPokemon()!; game.move.select(move); + await game.forceEnemyMove(Moves.WHIRLWIND); await game.phaseInterceptor.to("BerryPhase", false); expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined(); expect(game.scene.getEnemyPokemon()!.getLastXMoves(1)[0].result).toBe(MoveResult.MISS); }); + + it("should force switches randomly", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE ]); + + const [ bulbasaur, charmander, squirtle ] = game.scene.getPlayerParty(); + + // Turn 1: Mock an RNG call that calls for switching to 1st backup Pokemon (Charmander) + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WHIRLWIND); + await game.toNextTurn(); + + expect(bulbasaur.isOnField()).toBe(false); + expect(charmander.isOnField()).toBe(true); + expect(squirtle.isOnField()).toBe(false); + + // Turn 2: Mock an RNG call that calls for switching to 2nd backup Pokemon (Squirtle) + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min + 1; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WHIRLWIND); + await game.toNextTurn(); + + expect(bulbasaur.isOnField()).toBe(false); + expect(charmander.isOnField()).toBe(false); + expect(squirtle.isOnField()).toBe(true); + }); + + it("should not force a switch to a challenge-ineligible Pokemon", async () => { + // Mono-Water challenge, Eevee is ineligible + game.challengeMode.addChallenge(Challenges.SINGLE_TYPE, Type.WATER + 1, 0); + await game.challengeMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]); + + const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty(); + + // Turn 1: Mock an RNG call that would normally call for switching to Eevee, but it is ineligible + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WHIRLWIND); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(false); + expect(eevee.isOnField()).toBe(false); + expect(toxapex.isOnField()).toBe(true); + expect(primarina.isOnField()).toBe(false); + }); + + it("should not force a switch to a fainted Pokemon", async () => { + await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE, Species.TOXAPEX, Species.PRIMARINA ]); + + const [ lapras, eevee, toxapex, primarina ] = game.scene.getPlayerParty(); + + // Turn 1: Eevee faints + eevee.hp = 0; + eevee.status = new Status(StatusEffect.FAINT); + expect(eevee.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WHIRLWIND); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(false); + expect(eevee.isOnField()).toBe(false); + expect(toxapex.isOnField()).toBe(true); + expect(primarina.isOnField()).toBe(false); + }); + + it("should not force a switch if there are no available Pokemon to switch into", async () => { + await game.classicMode.startBattle([ Species.LAPRAS, Species.EEVEE ]); + + const [ lapras, eevee ] = game.scene.getPlayerParty(); + + // Turn 1: Eevee faints + eevee.hp = 0; + eevee.status = new Status(StatusEffect.FAINT); + expect(eevee.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + // Turn 2: Mock an RNG call that would normally call for switching to Eevee, but it is fainted + vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => { + return min; + }); + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.WHIRLWIND); + await game.toNextTurn(); + + expect(lapras.isOnField()).toBe(true); + expect(eevee.isOnField()).toBe(false); + }); }); diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index f95a442d4c2..ee67f1b5d39 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -1,18 +1,19 @@ -import { Button } from "#app/enums/buttons"; -import { MysteryEncounterBattlePhase, MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; -import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; -import { Mode } from "#app/ui/ui"; -import GameManager from "../utils/gameManager"; -import MessageUiHandler from "#app/ui/message-ui-handler"; -import { Status, StatusEffect } from "#app/data/status-effect"; -import { expect, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { Status } from "#app/data/status-effect"; +import { CommandPhase } from "#app/phases/command-phase"; +import { MessagePhase } from "#app/phases/message-phase"; +import { MysteryEncounterBattlePhase, MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; +import { VictoryPhase } from "#app/phases/victory-phase"; +import MessageUiHandler from "#app/ui/message-ui-handler"; +import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler"; import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import { Mode } from "#app/ui/ui"; import { isNullOrUndefined } from "#app/utils"; -import { CommandPhase } from "#app/phases/command-phase"; -import { VictoryPhase } from "#app/phases/victory-phase"; -import { MessagePhase } from "#app/phases/message-phase"; +import { Button } from "#enums/buttons"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; +import { expect, vi } from "vitest"; /** * Runs a {@linkcode MysteryEncounter} to either the start of a battle, or to the {@linkcode MysteryEncounterRewardsPhase}, depending on the option selected @@ -32,7 +33,7 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb }, () => game.isCurrentPhase(MysteryEncounterBattlePhase) || game.isCurrentPhase(MysteryEncounterRewardsPhase)); if (isBattle) { - game.onNextPrompt("DamagePhase", Mode.MESSAGE, () => { + game.onNextPrompt("DamageAnimPhase", Mode.MESSAGE, () => { game.setMode(Mode.MESSAGE); game.endPhase(); }, () => game.isCurrentPhase(CommandPhase)); diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index 7ea1f883bd1..e7ea6eea0ea 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -27,7 +27,7 @@ import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -266,6 +266,9 @@ describe("Clowning Around - Mystery Encounter", () => { // 5 Lucky Egg on lead (ultra) itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); + // 3 Soothe Bell on lead (great tier, but counted as ultra by this ME) + itemType = generateModifierType(scene, modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType; + await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType); // 5 Soul Dew on lead (rogue) itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); @@ -286,7 +289,7 @@ describe("Clowning Around - Mystery Encounter", () => { const rogueCountAfter = leadItemsAfter .filter(m => m.type.tier === ModifierTier.ROGUE) .reduce((a, b) => a + b.stackCount, 0); - expect(ultraCountAfter).toBe(10); + expect(ultraCountAfter).toBe(13); expect(rogueCountAfter).toBe(7); const secondItemsAfter = scene.getPlayerParty()[1].getHeldItems(); diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 92555e98679..215cab5c65a 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -1,8 +1,8 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { Gender } from "#app/data/gender"; @@ -13,8 +13,8 @@ import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRu import { Moves } from "#enums/moves"; import BattleScene from "#app/battle-scene"; import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { Type } from "#app/data/type"; -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Type } from "#enums/type"; +import { Status } from "#app/data/status-effect"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -25,6 +25,7 @@ import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Abilities } from "#enums/abilities"; import i18next from "i18next"; +import { StatusEffect } from "#enums/status-effect"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ diff --git a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index e8d19ff50b9..2c226df3c8c 100644 --- a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -18,6 +18,7 @@ import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { ModifierTier } from "#app/modifier/modifier-tier"; +import * as Utils from "#app/utils"; const namespace = "mysteryEncounters/globalTradeSystem"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -176,6 +177,23 @@ describe("Global Trade System - Mystery Encounter", () => { expect(defaultParty.includes(speciesAfter!)).toBeFalsy(); }); + it("Should roll for shiny twice, with random variant and associated luck", async () => { + // This ensures that the first shiny roll gets ignored, to test the ME rerolling for shiny + game.override.enemyShiny(false); + + await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty); + + vi.spyOn(Utils, "randSeedInt").mockReturnValue(1); // force shiny on reroll + + await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 }); + + const receivedPokemon = scene.getPlayerParty().at(-1)!; + + expect(receivedPokemon.shiny).toBeTruthy(); + expect(receivedPokemon.variant).toBeDefined(); + expect(receivedPokemon.luck).toBe(receivedPokemon.variant + 1); + }); + it("should leave encounter without battle", async () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); diff --git a/src/test/mystery-encounter/encounters/safari-zone.test.ts b/src/test/mystery-encounter/encounters/safari-zone.test.ts new file mode 100644 index 00000000000..5699afe254f --- /dev/null +++ b/src/test/mystery-encounter/encounters/safari-zone.test.ts @@ -0,0 +1,173 @@ +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; +import BattleScene from "#app/battle-scene"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { getSafariSpeciesSpawn, SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter"; +import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; + +const namespace = "mysteryEncounters/safariZone"; +const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; +const defaultBiome = Biome.SWAMP; +const defaultWave = 45; + +describe("Safari Zone - Mystery Encounter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + scene = game.scene; + game.override.mysteryEncounterChance(100); + game.override.startingWave(defaultWave); + game.override.startingBiome(defaultBiome); + game.override.disableTrainerWaves(); + + const biomeMap = new Map([ + [ Biome.VOLCANO, [ MysteryEncounterType.FIGHT_OR_FLIGHT ]], + [ Biome.FOREST, [ MysteryEncounterType.SAFARI_ZONE ]], + [ Biome.SWAMP, [ MysteryEncounterType.SAFARI_ZONE ]], + [ Biome.JUNGLE, [ MysteryEncounterType.SAFARI_ZONE ]], + ]); + vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.clearAllMocks(); + vi.resetAllMocks(); + }); + + it("should have the correct properties", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty); + + expect(SafariZoneEncounter.encounterType).toBe(MysteryEncounterType.SAFARI_ZONE); + expect(SafariZoneEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT); + expect(SafariZoneEncounter.dialogue).toBeDefined(); + expect(SafariZoneEncounter.dialogue.intro).toStrictEqual([ + { text: `${namespace}:intro` }, + ]); + expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`); + expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`); + expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`); + expect(SafariZoneEncounter.options.length).toBe(2); + }); + + it("should not spawn outside of the forest, swamp, or jungle biomes", async () => { + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); + game.override.startingBiome(Biome.VOLCANO); + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.SAFARI_ZONE); + }); + + it("should initialize fully", async () => { + initSceneWithoutEncounterPhase(scene, defaultParty); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(SafariZoneEncounter); + const encounter = scene.currentBattle.mysteryEncounter!; + scene.currentBattle.waveIndex = defaultWave; + + const { onInit } = encounter; + + expect(encounter.onInit).toBeDefined(); + + encounter.populateDialogueTokensFromRequirements(scene); + const onInitResult = onInit!(scene); + expect(onInitResult).toBe(true); + }); + + describe("Option 1 - Enter", () => { + it("should have the correct properties", () => { + const option = SafariZoneEncounter.options[0]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}:option.1.label`, + buttonTooltip: `${namespace}:option.1.tooltip`, + selected: [ + { + text: `${namespace}:option.1.selected`, + }, + ], + }); + }); + + it("should NOT be selectable if the player doesn't have enough money", async () => { + game.scene.money = 0; + await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty); + await game.phaseInterceptor.to(MysteryEncounterPhase, false); + + const encounterPhase = scene.getCurrentPhase(); + expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name); + const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase; + vi.spyOn(mysteryEncounterPhase, "continueEncounter"); + vi.spyOn(mysteryEncounterPhase, "handleOptionSelect"); + vi.spyOn(scene.ui, "playError"); + + await runSelectMysteryEncounterOption(game, 1); + + expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name); + expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled + expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled(); + expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); + }); + + it("should not spawn any Paradox Pokemon", async () => { + const NUM_ROLLS = 2000; // As long as this is greater than total number of species, this should cover all possible RNG rolls + let rngSweepProgress = 0; // Will simulate full range of RNG rolls by steadily increasing from 0 to 1 + + vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => { + return rngSweepProgress * (max - min) + min; + }); + vi.spyOn(Phaser.Math.RND, "shuffle").mockImplementation((arr: any[]) => arr); + + for (let i = 0; i < NUM_ROLLS; i++) { + rngSweepProgress = (2 * i + 1) / (2 * NUM_ROLLS); + const simSpecies = getSafariSpeciesSpawn().speciesId; + expect(NON_LEGEND_PARADOX_POKEMON).not.toContain(simSpecies); + } + }); + + // TODO: Tests for player actions inside the Safari Zone (Pokeball, Mud, Bait, Flee) + }); + + describe("Option 2 - Leave", () => { + it("should have the correct properties", () => { + const option = SafariZoneEncounter.options[1]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}:option.2.label`, + buttonTooltip: `${namespace}:option.2.tooltip`, + selected: [ + { + text: `${namespace}:option.2.selected`, + }, + ], + }); + }); + + it("should leave encounter without battle", async () => { + const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); + + await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty); + await runMysteryEncounterToEnd(game, 2); + + expect(leaveEncounterWithoutBattleSpy).toBeCalled(); + }); + }); +}); diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 7fc2490fcc9..87ccff71e22 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -18,6 +18,7 @@ import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/e import { TrainerType } from "#enums/trainer-type"; import { EggTier } from "#enums/egg-type"; import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; const namespace = "mysteryEncounters/theExpertPokemonBreeder"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -182,7 +183,10 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + // 20 from ME + extra from winning battle (that extra is not accurate to what happens in game. + // The Pokemon normally gets FRIENDSHIP_GAIN_FROM_BATTLE 3 times, once for each defeated Pokemon + // but due to how skipBattleRunMysteryEncounterRewardsPhase is implemented, it only receives it once) + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); }); }); @@ -261,7 +265,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 from ME + extra for friendship gained from winning battle }); }); @@ -340,7 +344,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { await game.phaseInterceptor.to(PostMysteryEncounterPhase); const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship; - expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle + expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 + extra for friendship gained from winning battle }); }); }); diff --git a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index a50c0cf4c9e..e90bc4efe56 100644 --- a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -9,11 +9,12 @@ import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test import BattleScene from "#app/battle-scene"; import { PlayerPokemon } from "#app/field/pokemon"; import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; -import { ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter"; +import { getSalesmanSpeciesOffer, ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups"; const namespace = "mysteryEncounters/thePokemonSalesman"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -122,7 +123,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { }); }); - it("Should update the player's money properly", async () => { + it("should update the player's money properly", async () => { const initialMoney = 20000; scene.money = initialMoney; const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney"); @@ -136,7 +137,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(scene.money).toBe(initialMoney - price); }); - it("Should add the Pokemon to the party", async () => { + it("should add the Pokemon to the party", async () => { scene.money = 20000; await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty); @@ -152,6 +153,18 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy(); }); + it("should give the purchased Pokemon its HA or make it shiny", async () => { + scene.money = 20000; + await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty); + await runMysteryEncounterToEnd(game, 1); + + const newlyPurchasedPokemon = scene.getPlayerParty()[scene.getPlayerParty().length - 1]; + const isshiny = newlyPurchasedPokemon.shiny; + const hasHA = newlyPurchasedPokemon.abilityIndex === 2; + expect(isshiny || hasHA).toBeTruthy(); + expect(isshiny && hasHA).toBeFalsy(); + }); + it("should be disabled if player does not have enough money", async () => { scene.money = 0; await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty); @@ -172,6 +185,22 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled(); }); + it("should not offer any Paradox Pokemon", async () => { + const NUM_ROLLS = 2000; // As long as this is greater than total number of species, this should cover all possible RNG rolls + let rngSweepProgress = 0; // Will simulate full range of RNG rolls by steadily increasing from 0 to 1 + + vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => { + return rngSweepProgress * (max - min) + min; + }); + vi.spyOn(Phaser.Math.RND, "shuffle").mockImplementation((arr: any[]) => arr); + + for (let i = 0; i < NUM_ROLLS; i++) { + rngSweepProgress = (2 * i + 1) / (2 * NUM_ROLLS); + const simSpecies = getSalesmanSpeciesOffer().speciesId; + expect(NON_LEGEND_PARADOX_POKEMON).not.toContain(simSpecies); + } + }); + it("should leave encounter without battle", async () => { scene.money = 20000; const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index be0e6e68b5e..5c965b13bd4 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -1,8 +1,8 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import * as BattleAnims from "#app/data/battle-anims"; @@ -11,7 +11,7 @@ import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } f import { Moves } from "#enums/moves"; import BattleScene from "#app/battle-scene"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { BerryType } from "#enums/berry-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonMove } from "#app/field/pokemon"; @@ -25,7 +25,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import { Abilities } from "#app/enums/abilities"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounters/theStrongStuff"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -109,6 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, + shiny: false, customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], diff --git a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 2653b76ab7c..701a3c94add 100644 --- a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -1,9 +1,9 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; @@ -19,12 +19,13 @@ import { Nature } from "#enums/nature"; import { Moves } from "#enums/moves"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter"; -import { Status, StatusEffect } from "#app/data/status-effect"; +import { Status } from "#app/data/status-effect"; import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; +import { StatusEffect } from "#enums/status-effect"; const namespace = "mysteryEncounters/theWinstrateChallenge"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; diff --git a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 8286c6a694b..f8d96487092 100644 --- a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -92,6 +92,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { { species: getPokemonSpecies(Species.GARBODOR), isBoss: true, + shiny: false, formIndex: 1, bossSegmentModifier: 1, moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ], diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index b57a88d5cad..d17b5c8a587 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -1,17 +1,17 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import GameManager from "#app/test/utils/gameManager"; -import Phaser from "phaser"; -import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import { StatusEffect } from "#app/data/status-effect"; -import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; -import { Type } from "#app/data/type"; -import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, getRandomPlayerPokemon, getRandomSpeciesByStarterTier, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { getEncounterText, queueEncounterMessage, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, getRandomPlayerPokemon, getRandomSpeciesByStarterCost, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { Type } from "#enums/type"; import { MessagePhase } from "#app/phases/message-phase"; +import GameManager from "#app/test/utils/gameManager"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Mystery Encounter Utils", () => { let phaserGame: Phaser.Game; @@ -204,9 +204,9 @@ describe("Mystery Encounter Utils", () => { }); }); - describe("getRandomSpeciesByStarterTier", () => { + describe("getRandomSpeciesByStarterCost", () => { it("gets species for a starter tier", () => { - const result = getRandomSpeciesByStarterTier(5); + const result = getRandomSpeciesByStarterCost(5); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(result); @@ -214,7 +214,7 @@ describe("Mystery Encounter Utils", () => { }); it("gets species for a starter tier range", () => { - const result = getRandomSpeciesByStarterTier([ 5, 8 ]); + const result = getRandomSpeciesByStarterCost([ 5, 8 ]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(result); @@ -224,14 +224,14 @@ describe("Mystery Encounter Utils", () => { it("excludes species from search", () => { // Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian - const result = getRandomSpeciesByStarterTier(9, [ Species.KORAIDON, Species.MIRAIDON, Species.ARCEUS, Species.RAYQUAZA, Species.KYOGRE, Species.GROUDON ]); + const result = getRandomSpeciesByStarterCost(9, [ Species.KORAIDON, Species.MIRAIDON, Species.ARCEUS, Species.RAYQUAZA, Species.KYOGRE, Species.GROUDON ]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(Species.ZACIAN); }); it("gets species of specified types", () => { // Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian - const result = getRandomSpeciesByStarterTier(9, undefined, [ Type.GROUND ]); + const result = getRandomSpeciesByStarterCost(9, undefined, [ Type.GROUND ]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(Species.GROUDON); }); diff --git a/src/test/phases/form-change-phase.test.ts b/src/test/phases/form-change-phase.test.ts new file mode 100644 index 00000000000..3c0016260a3 --- /dev/null +++ b/src/test/phases/form-change-phase.test.ts @@ -0,0 +1,60 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { Type } from "#enums/type"; +import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { modifierTypes } from "#app/modifier/modifier-type"; + +describe("Form Change Phase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("Zacian should successfully change into Crowned form", async () => { + await game.classicMode.startBattle([ Species.ZACIAN ]); + + // Before the form change: Should be Hero form + const zacian = game.scene.getPlayerParty()[0]; + expect(zacian.getFormKey()).toBe("hero-of-many-battles"); + expect(zacian.getTypes()).toStrictEqual([ Type.FAIRY ]); + expect(zacian.calculateBaseStats()).toStrictEqual([ 92, 120, 115, 80, 115, 138 ]); + + // Give Zacian a Rusted Sword + const rustedSwordType = generateModifierType(game.scene, modifierTypes.RARE_FORM_CHANGE_ITEM)!; + const rustedSword = rustedSwordType.newModifier(zacian); + await game.scene.addModifier(rustedSword); + + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + + // After the form change: Should be Crowned form + expect(game.phaseInterceptor.log.includes("FormChangePhase")).toBe(true); + expect(zacian.getFormKey()).toBe("crowned"); + expect(zacian.getTypes()).toStrictEqual([ Type.FAIRY, Type.STEEL ]); + expect(zacian.calculateBaseStats()).toStrictEqual([ 92, 150, 115, 80, 115, 148 ]); + }); +}); diff --git a/src/test/phases/game-over-phase.test.ts b/src/test/phases/game-over-phase.test.ts new file mode 100644 index 00000000000..2e19d5fe954 --- /dev/null +++ b/src/test/phases/game-over-phase.test.ts @@ -0,0 +1,77 @@ +import { Biome } from "#enums/biome"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { achvs } from "#app/system/achv"; +import { Unlockables } from "#app/system/unlockables"; + +describe("Game Over Phase", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MEMENTO, Moves.ICE_BEAM, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .startingWave(200) + .startingBiome(Biome.END) + .startingLevel(10000); + }); + + it("winning a run should give rewards", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + vi.spyOn(game.scene, "validateAchv"); + + // Note: `game.doKillOpponents()` does not properly handle final boss + // Final boss phase 1 + game.move.select(Moves.ICE_BEAM); + await game.toNextTurn(); + + // Final boss phase 2 + game.move.select(Moves.ICE_BEAM); + await game.phaseInterceptor.to("PostGameOverPhase", false); + + // The game refused to actually give the vouchers during tests, + // so the best we can do is to check that their reward phases occurred. + expect(game.phaseInterceptor.log.includes("GameOverPhase")).toBe(true); + expect(game.phaseInterceptor.log.includes("UnlockPhase")).toBe(true); + expect(game.phaseInterceptor.log.includes("RibbonModifierRewardPhase")).toBe(true); + expect(game.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]).toBe(true); + expect(game.scene.validateAchv).toHaveBeenCalledWith(achvs.CLASSIC_VICTORY); + expect(game.scene.gameData.achvUnlocks[achvs.CLASSIC_VICTORY.id]).toBeTruthy(); + }); + + it("losing a run should not give rewards", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + vi.spyOn(game.scene, "validateAchv"); + + game.move.select(Moves.MEMENTO); + await game.phaseInterceptor.to("PostGameOverPhase", false); + + expect(game.phaseInterceptor.log.includes("GameOverPhase")).toBe(true); + expect(game.phaseInterceptor.log.includes("UnlockPhase")).toBe(false); + expect(game.phaseInterceptor.log.includes("RibbonModifierRewardPhase")).toBe(false); + expect(game.phaseInterceptor.log.includes("GameOverModifierRewardPhase")).toBe(false); + expect(game.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]).toBe(false); + expect(game.scene.validateAchv).not.toHaveBeenCalledWith(achvs.CLASSIC_VICTORY); + expect(game.scene.gameData.achvUnlocks[achvs.CLASSIC_VICTORY.id]).toBeFalsy(); + }); +}); diff --git a/src/test/plugins/api/pokerogue-account-api.test.ts b/src/test/plugins/api/pokerogue-account-api.test.ts new file mode 100644 index 00000000000..90a7d3639ad --- /dev/null +++ b/src/test/plugins/api/pokerogue-account-api.test.ts @@ -0,0 +1,157 @@ +import type { AccountInfoResponse } from "#app/@types/PokerogueAccountApi"; +import { SESSION_ID_COOKIE_NAME } from "#app/constants"; +import { PokerogueAccountApi } from "#app/plugins/api/pokerogue-account-api"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import * as Utils from "#app/utils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const accountApi = new PokerogueAccountApi(apiBase); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue Account API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Get Info", () => { + it("should return account-info & 200 on SUCCESS", async () => { + const expectedAccountInfo: AccountInfoResponse = { + username: "test", + lastSessionSlot: -1, + discordId: "23235353543535", + googleId: "1ed1d1d11d1d1d1d1d1", + hasAdminRole: false, + }; + server.use(http.get(`${apiBase}/account/info`, () => HttpResponse.json(expectedAccountInfo))); + + const [ accountInfo, status ] = await accountApi.getInfo(); + + expect(accountInfo).toEqual(expectedAccountInfo); + expect(status).toBe(200); + }); + + it("should return null + status-code anad report a warning on FAILURE", async () => { + server.use(http.get(`${apiBase}/account/info`, () => new HttpResponse("", { status: 401 }))); + + const [ accountInfo, status ] = await accountApi.getInfo(); + + expect(accountInfo).toBeNull(); + expect(status).toBe(401); + expect(console.warn).toHaveBeenCalledWith("Could not get account info!", 401, "Unauthorized"); + }); + + it("should return null + 500 anad report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/account/info`, () => HttpResponse.error())); + + const [ accountInfo, status ] = await accountApi.getInfo(); + + expect(accountInfo).toBeNull(); + expect(status).toBe(500); + expect(console.warn).toHaveBeenCalledWith("Could not get account info!", expect.any(Error)); + }); + }); + + describe("Register", () => { + const registerParams = { username: "test", password: "test" }; + + it("should return null on SUCCESS", async () => { + server.use(http.post(`${apiBase}/account/register`, () => HttpResponse.text())); + + const error = await accountApi.register(registerParams); + + expect(error).toBeNull(); + }); + + it("should return error message on FAILURE", async () => { + server.use( + http.post(`${apiBase}/account/register`, () => new HttpResponse("Username is already taken", { status: 400 })) + ); + + const error = await accountApi.register(registerParams); + + expect(error).toBe("Username is already taken"); + }); + + it("should return \"Unknown error\" and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/account/register`, () => HttpResponse.error())); + + const error = await accountApi.register(registerParams); + + expect(error).toBe("Unknown error!"); + expect(console.warn).toHaveBeenCalledWith("Register failed!", expect.any(Error)); + }); + }); + + describe("Login", () => { + const loginParams = { username: "test", password: "test" }; + + it("should return null and set the cookie on SUCCESS", async () => { + vi.spyOn(Utils, "setCookie"); + server.use(http.post(`${apiBase}/account/login`, () => HttpResponse.json({ token: "abctest" }))); + + const error = await accountApi.login(loginParams); + + expect(error).toBeNull(); + expect(Utils.setCookie).toHaveBeenCalledWith(SESSION_ID_COOKIE_NAME, "abctest"); + }); + + it("should return error message and report a warning on FAILURE", async () => { + server.use( + http.post(`${apiBase}/account/login`, () => new HttpResponse("Password is incorrect", { status: 401 })) + ); + + const error = await accountApi.login(loginParams); + + expect(error).toBe("Password is incorrect"); + expect(console.warn).toHaveBeenCalledWith("Login failed!", 401, "Unauthorized"); + }); + + it("should return \"Unknown error\" and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/account/login`, () => HttpResponse.error())); + + const error = await accountApi.login(loginParams); + + expect(error).toBe("Unknown error!"); + expect(console.warn).toHaveBeenCalledWith("Login failed!", expect.any(Error)); + }); + }); + + describe("Logout", () => { + beforeEach(() => { + vi.spyOn(Utils, "removeCookie"); + }); + + it("should remove cookie on success", async () => { + vi.spyOn(Utils, "setCookie"); + server.use(http.get(`${apiBase}/account/logout`, () => new HttpResponse("", { status: 200 }))); + + await accountApi.logout(); + + expect(Utils.removeCookie).toHaveBeenCalledWith(SESSION_ID_COOKIE_NAME); + }); + + it("should report a warning on and remove cookie on FAILURE", async () => { + server.use(http.get(`${apiBase}/account/logout`, () => new HttpResponse("", { status: 401 }))); + + await accountApi.logout(); + + expect(Utils.removeCookie).toHaveBeenCalledWith(SESSION_ID_COOKIE_NAME); + expect(console.warn).toHaveBeenCalledWith("Log out failed!", expect.any(Error)); + }); + + it("should report a warning on and remove cookie on ERROR", async () => { + server.use(http.get(`${apiBase}/account/logout`, () => HttpResponse.error())); + + await accountApi.logout(); + + expect(Utils.removeCookie).toHaveBeenCalledWith(SESSION_ID_COOKIE_NAME); + expect(console.warn).toHaveBeenCalledWith("Log out failed!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-admin-api.test.ts b/src/test/plugins/api/pokerogue-admin-api.test.ts new file mode 100644 index 00000000000..5ae46abfcc8 --- /dev/null +++ b/src/test/plugins/api/pokerogue-admin-api.test.ts @@ -0,0 +1,232 @@ +import type { + LinkAccountToDiscordIdRequest, + LinkAccountToGoogledIdRequest, + SearchAccountRequest, + SearchAccountResponse, + UnlinkAccountFromDiscordIdRequest, + UnlinkAccountFromGoogledIdRequest, +} from "#app/@types/PokerogueAdminApi"; +import { PokerogueAdminApi } from "#app/plugins/api/pokerogue-admin-api"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const adminApi = new PokerogueAdminApi(apiBase); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue Admin API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Link Account to Discord", () => { + const params: LinkAccountToDiscordIdRequest = { username: "test", discordId: "test-12575756" }; + + it("should return null on SUCCESS", async () => { + server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.json(true))); + + const success = await adminApi.linkAccountToDiscord(params); + + expect(success).toBeNull(); + }); + + it("should return a ERR_GENERIC and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/admin/account/discordLink`, () => new HttpResponse("", { status: 400 }))); + + const success = await adminApi.linkAccountToDiscord(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 400, "Bad Request"); + }); + + it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => { + server.use(http.post(`${apiBase}/admin/account/discordLink`, () => new HttpResponse("", { status: 404 }))); + + const success = await adminApi.linkAccountToDiscord(params); + + expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND); + expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", 404, "Not Found"); + }); + + it("should return a ERR_GENERIC and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/admin/account/discordLink`, () => HttpResponse.error())); + + const success = await adminApi.linkAccountToDiscord(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not link account with discord!", expect.any(Error)); + }); + }); + + describe("Unlink Account from Discord", () => { + const params: UnlinkAccountFromDiscordIdRequest = { username: "test", discordId: "test-12575756" }; + + it("should return null on SUCCESS", async () => { + server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.json(true))); + + const success = await adminApi.unlinkAccountFromDiscord(params); + + expect(success).toBeNull(); + }); + + it("should return a ERR_GENERIC and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => new HttpResponse("", { status: 400 }))); + + const success = await adminApi.unlinkAccountFromDiscord(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 400, "Bad Request"); + }); + + it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => { + server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => new HttpResponse("", { status: 404 }))); + + const success = await adminApi.unlinkAccountFromDiscord(params); + + expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", 404, "Not Found"); + }); + + it("should return a ERR_GENERIC and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/admin/account/discordUnlink`, () => HttpResponse.error())); + + const success = await adminApi.unlinkAccountFromDiscord(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from discord!", expect.any(Error)); + }); + }); + + describe("Link Account to Google", () => { + const params: LinkAccountToGoogledIdRequest = { username: "test", googleId: "test-12575756" }; + + it("should return null on SUCCESS", async () => { + server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.json(true))); + + const success = await adminApi.linkAccountToGoogleId(params); + + expect(success).toBeNull(); + }); + + it("should return a ERR_GENERIC and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/admin/account/googleLink`, () => new HttpResponse("", { status: 400 }))); + + const success = await adminApi.linkAccountToGoogleId(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 400, "Bad Request"); + }); + + it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => { + server.use(http.post(`${apiBase}/admin/account/googleLink`, () => new HttpResponse("", { status: 404 }))); + + const success = await adminApi.linkAccountToGoogleId(params); + + expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND); + expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", 404, "Not Found"); + }); + + it("should return a ERR_GENERIC and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/admin/account/googleLink`, () => HttpResponse.error())); + + const success = await adminApi.linkAccountToGoogleId(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not link account with google!", expect.any(Error)); + }); + }); + + describe("Unlink Account from Google", () => { + const params: UnlinkAccountFromGoogledIdRequest = { username: "test", googleId: "test-12575756" }; + + it("should return null on SUCCESS", async () => { + server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.json(true))); + + const success = await adminApi.unlinkAccountFromGoogleId(params); + + expect(success).toBeNull(); + }); + + it("should return a ERR_GENERIC and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => new HttpResponse("", { status: 400 }))); + + const success = await adminApi.unlinkAccountFromGoogleId(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 400, "Bad Request"); + }); + + it("should return a ERR_USERNAME_NOT_FOUND and report a warning on 404", async () => { + server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => new HttpResponse("", { status: 404 }))); + + const success = await adminApi.unlinkAccountFromGoogleId(params); + + expect(success).toBe(adminApi.ERR_USERNAME_NOT_FOUND); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", 404, "Not Found"); + }); + + it("should return a ERR_GENERIC and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/admin/account/googleUnlink`, () => HttpResponse.error())); + + const success = await adminApi.unlinkAccountFromGoogleId(params); + + expect(success).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not unlink account from google!", expect.any(Error)); + }); + }); + + describe("Search Account", () => { + const params: SearchAccountRequest = { username: "test" }; + + it("should return [data, undefined] on SUCCESS", async () => { + const responseData: SearchAccountResponse = { + username: "test", + discordId: "discord-test-123", + googleId: "google-test-123", + lastLoggedIn: "2022-01-01", + registered: "2022-01-01", + }; + server.use(http.get(`${apiBase}/admin/account/adminSearch`, () => HttpResponse.json(responseData))); + + const [ data, err ] = await adminApi.searchAccount(params); + + expect(data).toStrictEqual(responseData); + expect(err).toBeUndefined(); + }); + + it("should return [undefined, ERR_GENERIC] and report a warning on on FAILURE", async () => { + server.use(http.get(`${apiBase}/admin/account/adminSearch`, () => new HttpResponse("", { status: 400 }))); + + const [ data, err ] = await adminApi.searchAccount(params); + + expect(data).toBeUndefined(); + expect(err).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not find account!", 400, "Bad Request"); + }); + + it("should return [undefined, ERR_USERNAME_NOT_FOUND] and report a warning on on 404", async () => { + server.use(http.get(`${apiBase}/admin/account/adminSearch`, () => new HttpResponse("", { status: 404 }))); + + const [ data, err ] = await adminApi.searchAccount(params); + + expect(data).toBeUndefined(); + expect(err).toBe(adminApi.ERR_USERNAME_NOT_FOUND); + expect(console.warn).toHaveBeenCalledWith("Could not find account!", 404, "Not Found"); + }); + + it("should return [undefined, ERR_GENERIC] and report a warning on on ERROR", async () => { + server.use(http.get(`${apiBase}/admin/account/adminSearch`, () => HttpResponse.error())); + + const [ data, err ] = await adminApi.searchAccount(params); + + expect(data).toBeUndefined(); + expect(err).toBe(adminApi.ERR_GENERIC); + expect(console.warn).toHaveBeenCalledWith("Could not find account!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-api.test.ts b/src/test/plugins/api/pokerogue-api.test.ts new file mode 100644 index 00000000000..a62174c226d --- /dev/null +++ b/src/test/plugins/api/pokerogue-api.test.ts @@ -0,0 +1,97 @@ +import type { TitleStatsResponse } from "#app/@types/PokerogueApi"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Game Title Stats", () => { + const expectedTitleStats: TitleStatsResponse = { + playerCount: 9999999, + battleCount: 9999999, + }; + + it("should return the stats on SUCCESS", async () => { + server.use(http.get(`${apiBase}/game/titlestats`, () => HttpResponse.json(expectedTitleStats))); + + const titleStats = await pokerogueApi.getGameTitleStats(); + + expect(titleStats).toEqual(expectedTitleStats); + }); + + it("should return null and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/game/titlestats`, () => HttpResponse.error())); + const titleStats = await pokerogueApi.getGameTitleStats(); + + expect(titleStats).toBeNull(); + expect(console.warn).toHaveBeenCalledWith("Could not get game title stats!", expect.any(Error)); + }); + }); + + describe("Unlink Discord", () => { + it("should return true on SUCCESS", async () => { + server.use(http.post(`${apiBase}/auth/discord/logout`, () => new HttpResponse("", { status: 200 }))); + + const success = await pokerogueApi.unlinkDiscord(); + + expect(success).toBe(true); + }); + + it("should return false and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/auth/discord/logout`, () => new HttpResponse("", { status: 401 }))); + + const success = await pokerogueApi.unlinkDiscord(); + + expect(success).toBe(false); + expect(console.warn).toHaveBeenCalledWith("Discord unlink failed (401: Unauthorized)"); + }); + + it("should return false and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/auth/discord/logout`, () => HttpResponse.error())); + + const success = await pokerogueApi.unlinkDiscord(); + + expect(success).toBe(false); + expect(console.warn).toHaveBeenCalledWith("Could not unlink Discord!", expect.any(Error)); + }); + }); + + describe("Unlink Google", () => { + it("should return true on SUCCESS", async () => { + server.use(http.post(`${apiBase}/auth/google/logout`, () => new HttpResponse("", { status: 200 }))); + + const success = await pokerogueApi.unlinkGoogle(); + + expect(success).toBe(true); + }); + + it("should return false and report a warning on FAILURE", async () => { + server.use(http.post(`${apiBase}/auth/google/logout`, () => new HttpResponse("", { status: 401 }))); + + const success = await pokerogueApi.unlinkGoogle(); + + expect(success).toBe(false); + expect(console.warn).toHaveBeenCalledWith("Google unlink failed (401: Unauthorized)"); + }); + + it("should return false and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/auth/google/logout`, () => HttpResponse.error())); + + const success = await pokerogueApi.unlinkGoogle(); + + expect(success).toBe(false); + expect(console.warn).toHaveBeenCalledWith("Could not unlink Google!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-daily-api.test.ts b/src/test/plugins/api/pokerogue-daily-api.test.ts new file mode 100644 index 00000000000..569e7cbb15d --- /dev/null +++ b/src/test/plugins/api/pokerogue-daily-api.test.ts @@ -0,0 +1,89 @@ +import type { GetDailyRankingsPageCountRequest, GetDailyRankingsRequest } from "#app/@types/PokerogueDailyApi"; +import { PokerogueDailyApi } from "#app/plugins/api/pokerogue-daily-api"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { ScoreboardCategory, type RankingEntry } from "#app/ui/daily-run-scoreboard"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const dailyApi = new PokerogueDailyApi(apiBase); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue Daily API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Get Seed", () => { + it("should return seed string on SUCCESS", async () => { + server.use(http.get(`${apiBase}/daily/seed`, () => HttpResponse.text("this-is-a-test-seed"))); + + const seed = await dailyApi.getSeed(); + + expect(seed).toBe("this-is-a-test-seed"); + }); + + it("should return null and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/daily/seed`, () => HttpResponse.error())); + + const seed = await dailyApi.getSeed(); + + expect(seed).toBeNull(); + expect(console.warn).toHaveBeenCalledWith("Could not get daily-run seed!", expect.any(Error)); + }); + }); + + describe("Get Rankings", () => { + const params: GetDailyRankingsRequest = { + category: ScoreboardCategory.DAILY, + }; + + it("should return ranking entries on SUCCESS", async () => { + const expectedRankings: RankingEntry[] = [ + { rank: 1, score: 999, username: "Player 1", wave: 200 }, + { rank: 2, score: 10, username: "Player 2", wave: 1 }, + ]; + server.use(http.get(`${apiBase}/daily/rankings`, () => HttpResponse.json(expectedRankings))); + + const rankings = await dailyApi.getRankings(params); + + expect(rankings).toStrictEqual(expectedRankings); + }); + + it("should return null and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/daily/rankings`, () => HttpResponse.error())); + + const rankings = await dailyApi.getRankings(params); + + expect(rankings).toBeNull(); + expect(console.warn).toHaveBeenCalledWith("Could not get daily rankings!", expect.any(Error)); + }); + }); + + describe("Get Rankings Page Count", () => { + const params: GetDailyRankingsPageCountRequest = { + category: ScoreboardCategory.DAILY, + }; + + it("should return a number on SUCCESS", async () => { + server.use(http.get(`${apiBase}/daily/rankingpagecount`, () => HttpResponse.json(5))); + + const pageCount = await dailyApi.getRankingsPageCount(params); + + expect(pageCount).toBe(5); + }); + + it("should return 1 and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/daily/rankingpagecount`, () => HttpResponse.error())); + + const pageCount = await dailyApi.getRankingsPageCount(params); + + expect(pageCount).toBe(1); + expect(console.warn).toHaveBeenCalledWith("Could not get daily rankings page count!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-savedata-api.test.ts b/src/test/plugins/api/pokerogue-savedata-api.test.ts new file mode 100644 index 00000000000..6dd402206e5 --- /dev/null +++ b/src/test/plugins/api/pokerogue-savedata-api.test.ts @@ -0,0 +1,46 @@ +import type { UpdateAllSavedataRequest } from "#app/@types/PokerogueSavedataApi"; +import { PokerogueSavedataApi } from "#app/plugins/api/pokerogue-savedata-api"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const savedataApi = new PokerogueSavedataApi(apiBase); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue Savedata API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Update All", () => { + it("should return an empty string on SUCCESS", async () => { + server.use(http.post(`${apiBase}/savedata/updateall`, () => HttpResponse.text(null))); + + const error = await savedataApi.updateAll({} as UpdateAllSavedataRequest); + + expect(error).toBe(""); + }); + + it("should return an error message on FAILURE", async () => { + server.use(http.post(`${apiBase}/savedata/updateall`, () => HttpResponse.text("Failed to update all!"))); + + const error = await savedataApi.updateAll({} as UpdateAllSavedataRequest); + + expect(error).toBe("Failed to update all!"); + }); + + it("should return 'Unknown error' and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/savedata/updateall`, () => HttpResponse.error())); + + const error = await savedataApi.updateAll({} as UpdateAllSavedataRequest); + + expect(error).toBe("Unknown error"); + expect(console.warn).toHaveBeenCalledWith("Could not update all savedata!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-session-savedata-api.test.ts b/src/test/plugins/api/pokerogue-session-savedata-api.test.ts new file mode 100644 index 00000000000..f453c5edd88 --- /dev/null +++ b/src/test/plugins/api/pokerogue-session-savedata-api.test.ts @@ -0,0 +1,200 @@ +import type { + ClearSessionSavedataRequest, + ClearSessionSavedataResponse, + DeleteSessionSavedataRequest, + GetSessionSavedataRequest, + NewClearSessionSavedataRequest, + UpdateSessionSavedataRequest, +} from "#app/@types/PokerogueSessionSavedataApi"; +import { PokerogueSessionSavedataApi } from "#app/plugins/api/pokerogue-session-savedata-api"; +import type { SessionSaveData } from "#app/system/game-data"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const sessionSavedataApi = new PokerogueSessionSavedataApi(apiBase); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue Session Savedata API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Newclear", () => { + const params: NewClearSessionSavedataRequest = { + clientSessionId: "test-session-id", + isVictory: true, + slot: 3 + }; + + it("should return true on SUCCESS", async () => { + server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.json(true))); + + const success = await sessionSavedataApi.newclear(params); + + expect(success).toBe(true); + }); + + it("should return false on FAILURE", async () => { + server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.json(false))); + + const success = await sessionSavedataApi.newclear(params); + + expect(success).toBe(false); + }); + + it("should return false and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/savedata/session/newclear`, () => HttpResponse.error())); + + const success = await sessionSavedataApi.newclear(params); + + expect(success).toBe(false); + expect(console.warn).toHaveBeenCalledWith("Could not newclear session!", expect.any(Error)); + }); + }); + + describe("Get ", () => { + const params: GetSessionSavedataRequest = { + clientSessionId: "test-session-id", + slot: 3, + }; + + it("should return session-savedata string on SUCCESS", async () => { + server.use(http.get(`${apiBase}/savedata/session/get`, () => HttpResponse.text("TEST SESSION SAVEDATA"))); + + const savedata = await sessionSavedataApi.get(params); + + expect(savedata).toBe("TEST SESSION SAVEDATA"); + }); + + it("should return null and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/savedata/session/get`, () => HttpResponse.error())); + + const savedata = await sessionSavedataApi.get(params); + + expect(savedata).toBeNull(); + expect(console.warn).toHaveBeenCalledWith("Could not get session savedata!", expect.any(Error)); + }); + }); + + describe("Update", () => { + const params: UpdateSessionSavedataRequest = { + clientSessionId: "test-session-id", + slot: 3, + secretId: 9876543321, + trainerId: 123456789, + }; + + it("should return an empty string on SUCCESS", async () => { + server.use(http.post(`${apiBase}/savedata/session/update`, () => HttpResponse.text(null))); + + const error = await sessionSavedataApi.update(params, "UPDATED SESSION SAVEDATA"); + + expect(error).toBe(""); + }); + + it("should return an error string on FAILURE", async () => { + server.use(http.post(`${apiBase}/savedata/session/update`, () => HttpResponse.text("Failed to update!"))); + + const error = await sessionSavedataApi.update(params, "UPDATED SESSION SAVEDATA"); + + expect(error).toBe("Failed to update!"); + }); + + it("should return 'Unknown Error!' and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/savedata/session/update`, () => HttpResponse.error())); + + const error = await sessionSavedataApi.update(params, "UPDATED SESSION SAVEDATA"); + + expect(error).toBe("Unknown Error!"); + expect(console.warn).toHaveBeenCalledWith("Could not update session savedata!", expect.any(Error)); + }); + }); + + describe("Delete", () => { + const params: DeleteSessionSavedataRequest = { + clientSessionId: "test-session-id", + slot: 3, + }; + + it("should return null on SUCCESS", async () => { + server.use(http.get(`${apiBase}/savedata/session/delete`, () => HttpResponse.text(null))); + + const error = await sessionSavedataApi.delete(params); + + expect(error).toBeNull(); + }); + + it("should return an error string on FAILURE", async () => { + server.use( + http.get(`${apiBase}/savedata/session/delete`, () => new HttpResponse("Failed to delete!", { status: 400 })) + ); + + const error = await sessionSavedataApi.delete(params); + + expect(error).toBe("Failed to delete!"); + }); + + it("should return 'Unknown error' and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/savedata/session/delete`, () => HttpResponse.error())); + + const error = await sessionSavedataApi.delete(params); + + expect(error).toBe("Unknown error"); + expect(console.warn).toHaveBeenCalledWith("Could not delete session savedata!", expect.any(Error)); + }); + }); + + describe("Clear", () => { + const params: ClearSessionSavedataRequest = { + clientSessionId: "test-session-id", + slot: 3, + trainerId: 123456789, + }; + + it("should return sucess=true on SUCCESS", async () => { + server.use( + http.post(`${apiBase}/savedata/session/clear`, () => + HttpResponse.json({ + success: true, + }) + ) + ); + + const { success, error } = await sessionSavedataApi.clear(params, {} as SessionSaveData); + + expect(success).toBe(true); + expect(error).toBeUndefined(); + }); + + it("should return sucess=false & an error string on FAILURE", async () => { + server.use( + http.post(`${apiBase}/savedata/session/clear`, () => + HttpResponse.json({ + success: false, + error: "Failed to clear!", + }) + ) + ); + + const { success, error } = await sessionSavedataApi.clear(params, {} as SessionSaveData); + + expect(error).toBe("Failed to clear!"); + expect(success).toBe(false); + }); + + it("should return success=false & error='Unknown error' and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.error())); + + const { success, error } = await sessionSavedataApi.clear(params, {} as SessionSaveData); + + expect(error).toBe("Unknown error"); + expect(success).toBe(false); + }); + }); +}); diff --git a/src/test/plugins/api/pokerogue-system-savedata-api.test.ts b/src/test/plugins/api/pokerogue-system-savedata-api.test.ts new file mode 100644 index 00000000000..af377762b77 --- /dev/null +++ b/src/test/plugins/api/pokerogue-system-savedata-api.test.ts @@ -0,0 +1,122 @@ +import type { + GetSystemSavedataRequest, + UpdateSystemSavedataRequest, + VerifySystemSavedataRequest, + VerifySystemSavedataResponse, +} from "#app/@types/PokerogueSystemSavedataApi"; +import { PokerogueSystemSavedataApi } from "#app/plugins/api/pokerogue-system-savedata-api"; +import type { SystemSaveData } from "#app/system/game-data"; +import { getApiBaseUrl } from "#app/test/utils/testUtils"; +import { http, HttpResponse } from "msw"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const apiBase = getApiBaseUrl(); +const systemSavedataApi = new PokerogueSystemSavedataApi(getApiBaseUrl()); +const { server } = global; + +afterEach(() => { + server.resetHandlers(); +}); + +describe("Pokerogue System Savedata API", () => { + beforeEach(() => { + vi.spyOn(console, "warn"); + }); + + describe("Get", () => { + const params: GetSystemSavedataRequest = { + clientSessionId: "test-session-id", + }; + + it("should return system-savedata string on SUCCESS", async () => { + server.use(http.get(`${apiBase}/savedata/system/get`, () => HttpResponse.text("TEST SYSTEM SAVEDATA"))); + + const savedata = await systemSavedataApi.get(params); + + expect(savedata).toBe("TEST SYSTEM SAVEDATA"); + }); + + it("should return null and report a warning on ERROR", async () => { + server.use(http.get(`${apiBase}/savedata/system/get`, () => HttpResponse.error())); + + const savedata = await systemSavedataApi.get(params); + + expect(savedata).toBeNull(); + expect(console.warn).toHaveBeenCalledWith("Could not get system savedata!", expect.any(Error)); + }); + }); + + describe("Verify", () => { + const params: VerifySystemSavedataRequest = { + clientSessionId: "test-session-id", + }; + + it("should return null on SUCCESS", async () => { + server.use( + http.get(`${apiBase}/savedata/system/verify`, () => + HttpResponse.json({ + systemData: { + trainerId: 123456789, + } as SystemSaveData, + valid: true, + }) + ) + ); + + const savedata = await systemSavedataApi.verify(params); + + expect(savedata).toBeNull(); + }); + + it("should return system-savedata and report a warning on FAILURE", async () => { + server.use( + http.get(`${apiBase}/savedata/system/verify`, () => + HttpResponse.json({ + systemData: { + trainerId: 123456789, + } as SystemSaveData, + valid: false, + }) + ) + ); + + const savedata = await systemSavedataApi.verify(params); + + expect(savedata?.trainerId).toBe(123456789); + expect(console.warn).toHaveBeenCalledWith("Invalid system savedata!"); + }); + }); + + describe("Update", () => { + const params: UpdateSystemSavedataRequest = { + clientSessionId: "test-session-id", + secretId: 9876543321, + trainerId: 123456789, + }; + + it("should return an empty string on SUCCESS", async () => { + server.use(http.post(`${apiBase}/savedata/system/update`, () => HttpResponse.text(null))); + + const error = await systemSavedataApi.update(params, "UPDATED SYSTEM SAVEDATA"); + + expect(error).toBe(""); + }); + + it("should return an error string on FAILURE", async () => { + server.use(http.post(`${apiBase}/savedata/system/update`, () => HttpResponse.text("Failed to update!"))); + + const error = await systemSavedataApi.update(params, "UPDATED SYSTEM SAVEDATA"); + + expect(error).toBe("Failed to update!"); + }); + + it("should return 'Unknown Error' and report a warning on ERROR", async () => { + server.use(http.post(`${apiBase}/savedata/system/update`, () => HttpResponse.error())); + + const error = await systemSavedataApi.update(params, "UPDATED SYSTEM SAVEDATA"); + + expect(error).toBe("Unknown Error"); + expect(console.warn).toHaveBeenCalledWith("Could not update system savedata!", expect.any(Error)); + }); + }); +}); diff --git a/src/test/reload.test.ts b/src/test/reload.test.ts index b15e9691ed6..3b29cadf8e7 100644 --- a/src/test/reload.test.ts +++ b/src/test/reload.test.ts @@ -1,4 +1,5 @@ import { GameModes } from "#app/game-mode"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { Biome } from "#enums/biome"; @@ -7,7 +8,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { MockClock } from "#test/utils/mocks/mockClock"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Reload", () => { let phaserGame: Phaser.Game; @@ -25,6 +26,8 @@ describe("Reload", () => { beforeEach(() => { game = new GameManager(phaserGame); + vi.spyOn(pokerogueApi, "getGameTitleStats").mockResolvedValue({ battleCount: -1, playerCount: -1 }); + vi.spyOn(pokerogueApi.daily, "getSeed").mockResolvedValue("test-seed"); }); it("should not have RNG inconsistencies in a Classic run", async () => { @@ -110,8 +113,7 @@ describe("Reload", () => { }, 20000); it("should not have RNG inconsistencies at a Daily run double battle", async () => { - game.override - .battleType("double"); + game.override.battleType("double"); await game.dailyMode.startBattle(); const preReloadRngState = Phaser.Math.RND.state(); @@ -124,9 +126,7 @@ describe("Reload", () => { }, 20000); it("should not have RNG inconsistencies at a Daily run Gym Leader fight", async () => { - game.override - .battleType("single") - .startingWave(40); + game.override.battleType("single").startingWave(40); await game.dailyMode.startBattle(); const preReloadRngState = Phaser.Math.RND.state(); @@ -139,9 +139,7 @@ describe("Reload", () => { }, 20000); it("should not have RNG inconsistencies at a Daily run regular trainer fight", async () => { - game.override - .battleType("single") - .startingWave(45); + game.override.battleType("single").startingWave(45); await game.dailyMode.startBattle(); const preReloadRngState = Phaser.Math.RND.state(); @@ -154,9 +152,7 @@ describe("Reload", () => { }, 20000); it("should not have RNG inconsistencies at a Daily run wave 50 Boss fight", async () => { - game.override - .battleType("single") - .startingWave(50); + game.override.battleType("single").startingWave(50); await game.runToFinalBossEncounter([ Species.BULBASAUR ], GameModes.DAILY); const preReloadRngState = Phaser.Math.RND.state(); diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts index fcb7e9067a3..1e349470302 100644 --- a/src/test/system/game_data.test.ts +++ b/src/test/system/game_data.test.ts @@ -1,36 +1,23 @@ import * as BattleScene from "#app/battle-scene"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { SessionSaveData } from "#app/system/game-data"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import GameManager from "#test/utils/gameManager"; -import { http, HttpResponse } from "msw"; -import { setupServer } from "msw/node"; import Phaser from "phaser"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as account from "../../account"; -const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; - -/** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */ -const server = setupServer(); - describe("System - Game Data", () => { let phaserGame: Phaser.Game; let game: GameManager; beforeAll(() => { - global.i18nServer.close(); - server.listen(); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, }); }); - afterAll(() => { - server.close(); - global.i18nServer.listen(); - }); - beforeEach(() => { game = new GameManager(phaserGame); game.override @@ -41,7 +28,6 @@ describe("System - Game Data", () => { }); afterEach(() => { - server.resetHandlers(); game.phaseInterceptor.restoreOg(); }); @@ -61,7 +47,7 @@ describe("System - Game Data", () => { }); it("should return [true, true] if successful", async () => { - server.use(http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ success: true }))); + vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ success: true }); const result = await game.scene.gameData.tryClearSession(game.scene, 0); @@ -70,7 +56,7 @@ describe("System - Game Data", () => { }); it("should return [true, false] if not successful", async () => { - server.use(http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ success: false }))); + vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ success: false }); const result = await game.scene.gameData.tryClearSession(game.scene, 0); @@ -79,9 +65,7 @@ describe("System - Game Data", () => { }); it("should return [false, false] session is out of date", async () => { - server.use( - http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ error: "session out of date" })) - ); + vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ error: "session out of date" }); const result = await game.scene.gameData.tryClearSession(game.scene, 0); diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 8f86d62c546..15af3619ed3 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -1,5 +1,5 @@ import { Gender } from "#app/data/gender"; -import { Nature } from "#app/data/nature"; +import { Nature } from "#enums/nature"; import { allSpecies } from "#app/data/pokemon-species"; import { GameModes } from "#app/game-mode"; import { EncounterPhase } from "#app/phases/encounter-phase"; diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index 22517502a05..ca5a67f901a 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -79,7 +79,7 @@ export default class GameWrapper { constructor(phaserGame: Phaser.Game, bypassLogin: boolean) { Phaser.Math.RND.sow([ 'test' ]); - vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch); + // vi.spyOn(Utils, "apiFetch", "get").mockReturnValue(fetch); if (bypassLogin) { vi.spyOn(battleScene, "bypassLogin", "get").mockReturnValue(true); } diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 80d0b86de7b..41a21a52b72 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -35,7 +35,7 @@ export class ClassicModeHelper extends GameManagerHelper { selectStarterPhase.initBattle(starters); }); - await this.game.phaseInterceptor.run(EncounterPhase); + await this.game.phaseInterceptor.to(EncounterPhase); if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index 73fe63395fd..68d3b3d51d7 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,4 +1,6 @@ import { BattlerIndex } from "#app/battle"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import { CommandPhase } from "#app/phases/command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; @@ -71,4 +73,21 @@ export class MoveHelper extends GameManagerHelper { await this.game.phaseInterceptor.to("MovePhase"); vi.spyOn(Overrides, "STATUS_ACTIVATION_OVERRIDE", "get").mockReturnValue(null); } + + /** + * Used when the normal moveset override can't be used (such as when it's necessary to check updated properties of the moveset). + * @param pokemon - The pokemon being modified + * @param moveset - The moveset to use + */ + public changeMoveset(pokemon: Pokemon, moveset: Moves | Moves[]): void { + if (!Array.isArray(moveset)) { + moveset = [ moveset ]; + } + pokemon.moveset = []; + moveset.forEach((move) => { + pokemon.moveset.push(new PokemonMove(move)); + }); + const movesetStr = moveset.map((moveId) => Moves[moveId]).join(", "); + console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`); + } } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 404f5c34a26..1c05f92a334 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -1,19 +1,20 @@ -import { StatusEffect } from "#app/data/status-effect"; -import { Weather, WeatherType } from "#app/data/weather"; +import { Variant } from "#app/data/variant"; +import { Weather } from "#app/data/weather"; import { Abilities } from "#app/enums/abilities"; -import { Biome } from "#app/enums/biome"; -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; import * as GameMode from "#app/game-mode"; import { GameModes, getGameMode } from "#app/game-mode"; import { ModifierOverride } from "#app/modifier/modifier-type"; -import Overrides from "#app/overrides"; +import Overrides, { BattleStyle } from "#app/overrides"; +import { Unlockables } from "#app/system/unlockables"; +import { Biome } from "#enums/biome"; +import { Moves } from "#enums/moves"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import { StatusEffect } from "#enums/status-effect"; +import type { WeatherType } from "#enums/weather-type"; import { vi } from "vitest"; import { GameManagerHelper } from "./gameManagerHelper"; -import { Unlockables } from "#app/system/unlockables"; -import { Variant } from "#app/data/variant"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; /** * Helper to handle overrides in tests @@ -237,13 +238,14 @@ export class OverridesHelper extends GameManagerHelper { } /** - * Override the battle type (single or double) + * Override the battle type (e.g., single or double). + * @see {@linkcode Overrides.BATTLE_TYPE_OVERRIDE} * @param battleType battle type to set * @returns `this` */ - public battleType(battleType: "single" | "double" | null): this { + public battleType(battleType: BattleStyle | null): this { vi.spyOn(Overrides, "BATTLE_TYPE_OVERRIDE", "get").mockReturnValue(battleType); - this.log(`Battle type set to ${battleType} only!`); + this.log(battleType === null ? "Battle type override disabled!" : `Battle type set to ${battleType}!`); return this; } diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index ec9309e2405..13750609004 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -5,13 +5,14 @@ import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { BerryPhase } from "#app/phases/berry-phase"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CommandPhase } from "#app/phases/command-phase"; -import { DamagePhase } from "#app/phases/damage-phase"; +import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { EvolutionPhase } from "#app/phases/evolution-phase"; import { FaintPhase } from "#app/phases/faint-phase"; +import { FormChangePhase } from "#app/phases/form-change-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LevelCapPhase } from "#app/phases/level-cap-phase"; import { LoginPhase } from "#app/phases/login-phase"; @@ -54,6 +55,11 @@ import { import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; import { ExpPhase } from "#app/phases/exp-phase"; +import { GameOverPhase } from "#app/phases/game-over-phase"; +import { RibbonModifierRewardPhase } from "#app/phases/ribbon-modifier-reward-phase"; +import { GameOverModifierRewardPhase } from "#app/phases/game-over-modifier-reward-phase"; +import { UnlockPhase } from "#app/phases/unlock-phase"; +import { PostGameOverPhase } from "#app/phases/post-game-over-phase"; export interface PromptHandler { phaseTarget?: string; @@ -67,7 +73,6 @@ type PhaseClass = | typeof LoginPhase | typeof TitlePhase | typeof SelectGenderPhase - | typeof EncounterPhase | typeof NewBiomeEncounterPhase | typeof SelectStarterPhase | typeof PostSummonPhase @@ -82,7 +87,7 @@ type PhaseClass = | typeof TurnStartPhase | typeof MovePhase | typeof MoveEffectPhase - | typeof DamagePhase + | typeof DamageAnimPhase | typeof FaintPhase | typeof BerryPhase | typeof TurnEndPhase @@ -102,6 +107,7 @@ type PhaseClass = | typeof SwitchPhase | typeof SwitchSummonPhase | typeof PartyHealPhase + | typeof FormChangePhase | typeof EvolutionPhase | typeof EndEvolutionPhase | typeof LevelCapPhase @@ -112,15 +118,20 @@ type PhaseClass = | typeof MysteryEncounterBattlePhase | typeof MysteryEncounterRewardsPhase | typeof PostMysteryEncounterPhase + | typeof RibbonModifierRewardPhase + | typeof GameOverModifierRewardPhase | typeof ModifierRewardPhase | typeof PartyExpPhase - | typeof ExpPhase; + | typeof ExpPhase + | typeof EncounterPhase + | typeof GameOverPhase + | typeof UnlockPhase + | typeof PostGameOverPhase; type PhaseString = | "LoginPhase" | "TitlePhase" | "SelectGenderPhase" - | "EncounterPhase" | "NewBiomeEncounterPhase" | "SelectStarterPhase" | "PostSummonPhase" @@ -135,7 +146,7 @@ type PhaseString = | "TurnStartPhase" | "MovePhase" | "MoveEffectPhase" - | "DamagePhase" + | "DamageAnimPhase" | "FaintPhase" | "BerryPhase" | "TurnEndPhase" @@ -155,6 +166,7 @@ type PhaseString = | "SwitchPhase" | "SwitchSummonPhase" | "PartyHealPhase" + | "FormChangePhase" | "EvolutionPhase" | "EndEvolutionPhase" | "LevelCapPhase" @@ -165,9 +177,15 @@ type PhaseString = | "MysteryEncounterBattlePhase" | "MysteryEncounterRewardsPhase" | "PostMysteryEncounterPhase" + | "RibbonModifierRewardPhase" + | "GameOverModifierRewardPhase" | "ModifierRewardPhase" | "PartyExpPhase" - | "ExpPhase"; + | "ExpPhase" + | "EncounterPhase" + | "GameOverPhase" + | "UnlockPhase" + | "PostGameOverPhase"; type PhaseInterceptorPhase = PhaseClass | PhaseString; @@ -187,12 +205,16 @@ export default class PhaseInterceptor { /** * List of phases with their corresponding start methods. + * + * CAUTION: If a phase and its subclasses (if any) both appear in this list, + * make sure that this list contains said phase AFTER all of its subclasses. + * This way, the phase's `prototype.start` is properly preserved during + * `initPhases()` so that its subclasses can use `super.start()` properly. */ private PHASES = [ [ LoginPhase, this.startPhase ], [ TitlePhase, this.startPhase ], [ SelectGenderPhase, this.startPhase ], - [ EncounterPhase, this.startPhase ], [ NewBiomeEncounterPhase, this.startPhase ], [ SelectStarterPhase, this.startPhase ], [ PostSummonPhase, this.startPhase ], @@ -207,7 +229,7 @@ export default class PhaseInterceptor { [ TurnStartPhase, this.startPhase ], [ MovePhase, this.startPhase ], [ MoveEffectPhase, this.startPhase ], - [ DamagePhase, this.startPhase ], + [ DamageAnimPhase, this.startPhase ], [ FaintPhase, this.startPhase ], [ BerryPhase, this.startPhase ], [ TurnEndPhase, this.startPhase ], @@ -227,6 +249,7 @@ export default class PhaseInterceptor { [ SwitchPhase, this.startPhase ], [ SwitchSummonPhase, this.startPhase ], [ PartyHealPhase, this.startPhase ], + [ FormChangePhase, this.startPhase ], [ EvolutionPhase, this.startPhase ], [ EndEvolutionPhase, this.startPhase ], [ LevelCapPhase, this.startPhase ], @@ -237,9 +260,15 @@ export default class PhaseInterceptor { [ MysteryEncounterBattlePhase, this.startPhase ], [ MysteryEncounterRewardsPhase, this.startPhase ], [ PostMysteryEncounterPhase, this.startPhase ], + [ RibbonModifierRewardPhase, this.startPhase ], + [ GameOverModifierRewardPhase, this.startPhase ], [ ModifierRewardPhase, this.startPhase ], [ PartyExpPhase, this.startPhase ], [ ExpPhase, this.startPhase ], + [ EncounterPhase, this.startPhase ], + [ GameOverPhase, this.startPhase ], + [ UnlockPhase, this.startPhase ], + [ PostGameOverPhase, this.startPhase ], ]; private endBySetMode = [ diff --git a/src/test/utils/saves/everything.prsv b/src/test/utils/saves/everything.prsv index 1985a6649ca..f9106ce0e35 100644 --- a/src/test/utils/saves/everything.prsv +++ b/src/test/utils/saves/everything.prsv @@ -1 +1 @@ -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 \ No newline at end of file +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 diff --git a/src/test/utils/testUtils.ts b/src/test/utils/testUtils.ts index b922fc9c61c..a8410f8ba40 100644 --- a/src/test/utils/testUtils.ts +++ b/src/test/utils/testUtils.ts @@ -21,3 +21,11 @@ export function mockI18next() { export function arrayOfRange(start: integer, end: integer) { return Array.from({ length: end - start }, (_v, k) => k + start); } + +/** + * Utility to get the API base URL from the environment variable (or the default/fallback). + * @returns the API base URL + */ +export function getApiBaseUrl() { + return import.meta.env.VITE_SERVER_URL ?? "http://localhost:8001"; +} diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 8438f607db2..0b83d112522 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -38,7 +38,7 @@ vi.mock("i18next", async (importOriginal) => { const { setupServer } = await import("msw/node"); const { http, HttpResponse } = await import("msw"); - global.i18nServer = setupServer( + global.server = setupServer( http.get("/locales/en/*", async (req) => { const filename = req.params[0]; @@ -50,9 +50,12 @@ vi.mock("i18next", async (importOriginal) => { console.log(`Failed to load locale ${filename}!`, err); return HttpResponse.json({}); } - }) + }), + http.get("https://fonts.googleapis.com/*", () => { + return HttpResponse.text(""); + }), ); - global.i18nServer.listen({ onUnhandledRequest: "error" }); + global.server.listen({ onUnhandledRequest: "error" }); console.log("i18n MSW server listening!"); return await importOriginal(); @@ -83,6 +86,6 @@ beforeAll(() => { }); afterAll(() => { - global.i18nServer.close(); + global.server.close(); console.log("Closing i18n MSW server!"); }); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 3b2b3619397..9515be7b49e 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -189,7 +189,7 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container { const secs = Math.round(diff % 6e4 / 1e3); // Return formatted string - return "Event Ends in : " + z(days) + "d " + z(hours) + "h " + z(mins) + "m " + z(secs) + "s"; + return i18next.t("menu:eventTimer", { days: z(days), hours: z(hours), mins: z(mins), secs: z(secs) }); } updateCountdown() { diff --git a/src/ui/admin-ui-handler.ts b/src/ui/admin-ui-handler.ts index 6249e54d8c3..269b5ac5096 100644 --- a/src/ui/admin-ui-handler.ts +++ b/src/ui/admin-ui-handler.ts @@ -1,10 +1,14 @@ import BattleScene from "#app/battle-scene"; -import { ModalConfig } from "./modal-ui-handler"; -import { Mode } from "./ui"; -import * as Utils from "../utils"; -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { Button } from "#app/enums/buttons"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { formatText } from "#app/utils"; +import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; +import { ModalConfig } from "./modal-ui-handler"; import { TextStyle } from "./text"; +import { Mode } from "./ui"; + +type AdminUiHandlerService = "discord" | "google"; +type AdminUiHandlerServiceMode = "Link" | "Unlink"; export default class AdminUiHandler extends FormModalUiHandler { @@ -17,17 +21,15 @@ export default class AdminUiHandler extends FormModalUiHandler { private readonly httpUserNotFoundErrorCode: number = 404; private readonly ERR_REQUIRED_FIELD = (field: string) => { if (field === "username") { - return `${Utils.formatText(field)} is required`; + return `${formatText(field)} is required`; } else { - return `${Utils.formatText(field)} Id is required`; + return `${formatText(field)} Id is required`; } }; // returns a string saying whether a username has been successfully linked/unlinked to discord/google private readonly SUCCESS_SERVICE_MODE = (service: string, mode: string) => { return `Username and ${service} successfully ${mode.toLowerCase()}ed`; }; - private readonly ERR_USERNAME_NOT_FOUND: string = "Username not found!"; - private readonly ERR_GENERIC_ERROR: string = "There was an error"; constructor(scene: BattleScene, mode: Mode | null = null) { super(scene, mode); @@ -148,7 +150,6 @@ export default class AdminUiHandler extends FormModalUiHandler { } else if (this.adminMode === AdminMode.ADMIN) { this.updateAdminPanelInfo(adminSearchResult, AdminMode.SEARCH); } - return false; }; return true; } @@ -196,7 +197,7 @@ export default class AdminUiHandler extends FormModalUiHandler { this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error return this.showMessage(validFields.errorMessage ?? "", adminResult, true); } - this.adminLinkUnlink(this.convertInputsToAdmin(), service, mode).then(response => { // attempts to link/unlink depending on the service + this.adminLinkUnlink(this.convertInputsToAdmin(), service as AdminUiHandlerService, mode).then(response => { // attempts to link/unlink depending on the service if (response.error) { this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); return this.showMessage(response.errorType, adminResult, true); // fail @@ -276,12 +277,11 @@ export default class AdminUiHandler extends FormModalUiHandler { private async adminSearch(adminSearchResult: AdminSearchInfo) { try { - const adminInfo = await Utils.apiFetch(`admin/account/adminSearch?username=${encodeURIComponent(adminSearchResult.username)}`, true); - if (!adminInfo.ok) { // error - if adminInfo.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db - return { adminSearchResult: adminSearchResult, error: true, errorType: adminInfo.status === this.httpUserNotFoundErrorCode ? this.ERR_USERNAME_NOT_FOUND : this.ERR_GENERIC_ERROR }; + const [ adminInfo, errorType ] = await pokerogueApi.admin.searchAccount({ username: adminSearchResult.username }); + if (errorType || !adminInfo) { // error - if adminInfo.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db + return { adminSearchResult: adminSearchResult, error: true, errorType }; } else { // success - const adminInfoJson: AdminSearchInfo = await adminInfo.json(); - return { adminSearchResult: adminInfoJson, error: false }; + return { adminSearchResult: adminInfo, error: false }; } } catch (err) { console.error(err); @@ -289,12 +289,47 @@ export default class AdminUiHandler extends FormModalUiHandler { } } - private async adminLinkUnlink(adminSearchResult: AdminSearchInfo, service: string, mode: string) { + private async adminLinkUnlink(adminSearchResult: AdminSearchInfo, service: AdminUiHandlerService, mode: AdminUiHandlerServiceMode) { try { - const response = await Utils.apiPost(`admin/account/${service}${mode}`, `username=${encodeURIComponent(adminSearchResult.username)}&${service}Id=${encodeURIComponent(service === "discord" ? adminSearchResult.discordId : adminSearchResult.googleId)}`, "application/x-www-form-urlencoded", true); - if (!response.ok) { // error - if response.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db - return { adminSearchResult: adminSearchResult, error: true, errorType: response.status === this.httpUserNotFoundErrorCode ? this.ERR_USERNAME_NOT_FOUND : this.ERR_GENERIC_ERROR }; - } else { // success! + let errorType: string | null = null; + + if (service === "discord") { + if (mode === "Link") { + errorType = await pokerogueApi.admin.linkAccountToDiscord({ + discordId: adminSearchResult.discordId, + username: adminSearchResult.username, + }); + } else if (mode === "Unlink") { + errorType = await pokerogueApi.admin.unlinkAccountFromDiscord({ + discordId: adminSearchResult.discordId, + username: adminSearchResult.username, + }); + } else { + console.warn("Unknown mode", mode, "for service", service); + } + } else if (service === "google") { + if (mode === "Link") { + errorType = await pokerogueApi.admin.linkAccountToGoogleId({ + googleId: adminSearchResult.googleId, + username: adminSearchResult.username, + }); + } else if (mode === "Unlink") { + errorType = await pokerogueApi.admin.unlinkAccountFromGoogleId({ + googleId: adminSearchResult.googleId, + username: adminSearchResult.username, + }); + } else { + console.warn("Unknown mode", mode, "for service", service); + } + } else { + console.warn("Unknown service", service); + } + + if (errorType) { + // error - if response.status === this.httpUserNotFoundErrorCode that means the username can't be found in the db + return { adminSearchResult: adminSearchResult, error: true, errorType }; + } else { + // success! return { adminSearchResult: adminSearchResult, error: false }; } } catch (err) { diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index a82f97244cd..573cb85db70 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -1,7 +1,7 @@ import { addTextObject, TextStyle } from "./text"; import BattleScene from "#app/battle-scene"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { WeatherType } from "#app/data/weather"; +import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { addWindow, WindowVariant } from "./ui-theme"; import { ArenaEvent, ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 1d97998f491..72447988bdd 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -3,9 +3,10 @@ import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; import * as Utils from "../utils"; import { addTextObject, TextStyle } from "./text"; import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; -import { StatusEffect } from "../data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import BattleScene from "../battle-scene"; -import { Type, getTypeRgb } from "../data/type"; +import { getTypeRgb } from "#app/data/type"; +import { Type } from "#enums/type"; import { getVariantTint } from "#app/data/variant"; import { Stat } from "#enums/stat"; import BattleFlyout from "./battle-flyout"; diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 0f5edc28675..0dacacc7b70 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -90,9 +90,6 @@ export default class CommandUiHandler extends UiHandler { switch (cursor) { // Fight case Command.FIGHT: - if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { - return true; - } ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); success = true; break; diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index b9c1c6ea49a..bb93b1fb1f5 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -3,8 +3,9 @@ import BattleScene from "../battle-scene"; import * as Utils from "../utils"; import { TextStyle, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; -interface RankingEntry { +export interface RankingEntry { rank: integer, username: string, score: integer, @@ -12,7 +13,7 @@ interface RankingEntry { } // Don't forget to update translations when adding a new category -enum ScoreboardCategory { +export enum ScoreboardCategory { DAILY, WEEKLY } @@ -191,18 +192,17 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { } Utils.executeIf(category !== this.category || this.pageCount === undefined, - () => Utils.apiFetch(`daily/rankingpagecount?category=${category}`).then(response => response.json()).then(count => this.pageCount = count) + () => pokerogueApi.daily.getRankingsPageCount({ category }).then(count => this.pageCount = count) ).then(() => { - Utils.apiFetch(`daily/rankings?category=${category}&page=${page}`) - .then(response => response.json()) - .then(jsonResponse => { + pokerogueApi.daily.getRankings({ category, page }) + .then(rankings => { this.page = page; this.category = category; this.titleLabel.setText(`${i18next.t(`menu:${ScoreboardCategory[category].toLowerCase()}Rankings`)}`); this.pageNumberLabel.setText(page.toString()); - if (jsonResponse) { + if (rankings) { this.loadingLabel.setVisible(false); - this.updateRankings(jsonResponse); + this.updateRankings(rankings); } else { this.loadingLabel.setText(i18next.t("menu:noRankings")); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index ee6641a1a27..eaf504495d5 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -1,6 +1,7 @@ import BattleScene, { InfoToggle } from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; -import { getTypeDamageMultiplierColor, Type } from "../data/type"; +import { getTypeDamageMultiplierColor } from "#app/data/type"; +import { Type } from "#enums/type"; import { Command } from "./command-ui-handler"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 26a2a225ec6..78755423604 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -7,6 +7,8 @@ import BattleScene from "#app/battle-scene"; import { addTextObject, TextStyle } from "./text"; import { addWindow } from "./ui-theme"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import JSZip from "jszip"; interface BuildInteractableImageOpts { scale?: number; @@ -26,6 +28,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { private googleImage: Phaser.GameObjects.Image; private discordImage: Phaser.GameObjects.Image; private usernameInfoImage: Phaser.GameObjects.Image; + private saveDownloadImage: Phaser.GameObjects.Image; private externalPartyContainer: Phaser.GameObjects.Container; private infoContainer: Phaser.GameObjects.Container; private externalPartyBg: Phaser.GameObjects.NineSlice; @@ -45,7 +48,13 @@ export default class LoginFormUiHandler extends FormModalUiHandler { scale: 0.5 }); + this.saveDownloadImage = this.buildInteractableImage("saving_icon", "save-download-icon", { + x: 0, + scale: 0.75 + }); + this.infoContainer.add(this.usernameInfoImage); + this.infoContainer.add(this.saveDownloadImage); this.getUi().add(this.infoContainer); this.infoContainer.setVisible(false); this.infoContainer.disableInteractive(); @@ -72,7 +81,11 @@ export default class LoginFormUiHandler extends FormModalUiHandler { } override getModalTitle(_config?: ModalConfig): string { - return i18next.t("menu:login"); + let key = "menu:login"; + if (import.meta.env.VITE_SERVER_URL === "https://apibeta.pokerogue.net") { + key = "menu:loginBeta"; + } + return i18next.t(key); } override getWidth(_config?: ModalConfig): number { @@ -135,21 +148,16 @@ export default class LoginFormUiHandler extends FormModalUiHandler { if (!this.inputs[0].text) { return onFail(i18next.t("menu:emptyUsername")); } - Utils.apiPost("account/login", `username=${encodeURIComponent(this.inputs[0].text)}&password=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded") - .then(response => { - if (!response.ok) { - return response.text(); - } - return response.json(); - }) - .then(response => { - if (response.hasOwnProperty("token")) { - Utils.setCookie(Utils.sessionIdKey, response.token); - originalLoginAction && originalLoginAction(); - } else { - onFail(response); - } - }); + + const [ usernameInput, passwordInput ] = this.inputs; + + pokerogueApi.account.login({ username: usernameInput.text, password: passwordInput.text }).then(error => { + if (!error) { + originalLoginAction && originalLoginAction(); + } else { + onFail(error); + } + }); }; return true; @@ -164,7 +172,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { this.infoContainer.setVisible(false); this.setMouseCursorStyle("default"); //reset cursor - [ this.discordImage, this.googleImage, this.usernameInfoImage ].forEach((img) => img.off("pointerdown")); + [ this.discordImage, this.googleImage, this.usernameInfoImage, this.saveDownloadImage ].forEach((img) => img.off("pointerdown")); } private processExternalProvider(config: ModalConfig): void { @@ -182,6 +190,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { this.infoContainer.setVisible(true); this.getUi().moveTo(this.infoContainer, this.getUi().length - 1); this.usernameInfoImage.setPositionRelative(this.infoContainer, 0, 0); + this.saveDownloadImage.setPositionRelative(this.infoContainer, 20, 0); this.discordImage.on("pointerdown", () => { const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); @@ -233,6 +242,34 @@ export default class LoginFormUiHandler extends FormModalUiHandler { } }); + this.saveDownloadImage.on("pointerdown", () => { + // find all data_ and sessionData keys, put them in a .txt file and download everything in a single zip + const localStorageKeys = Object.keys(localStorage); // this gets the keys for localStorage + const keyToFind = "data_"; + const sessionKeyToFind = "sessionData"; + const dataKeys = localStorageKeys.filter(ls => ls.indexOf(keyToFind) >= 0); + const sessionKeys = localStorageKeys.filter(ls => ls.indexOf(sessionKeyToFind) >= 0); + if (dataKeys.length > 0 || sessionKeys.length > 0) { + const zip = new JSZip(); + for (let i = 0; i < dataKeys.length; i++) { + zip.file(dataKeys[i] + ".prsv", localStorage.getItem(dataKeys[i])!); + } + for (let i = 0; i < sessionKeys.length; i++) { + zip.file(sessionKeys[i] + ".prsv", localStorage.getItem(sessionKeys[i])!); + } + zip.generateAsync({ type: "blob" }).then(content => { + const url = URL.createObjectURL(content); + const a = document.createElement("a"); + a.href = url; + a.download = "pokerogue_saves.zip"; + a.click(); + URL.revokeObjectURL(url); + }); + } else { + return onFail(this.ERR_NO_SAVES); + } + }); + this.externalPartyContainer.setAlpha(0); this.scene.tweens.add({ targets: this.externalPartyContainer, diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index fea0a70af91..3ce3f3b7cf0 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -14,6 +14,7 @@ import BgmBar from "#app/ui/bgm-bar"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { AdminMode, getAdminModeName } from "./admin-ui-handler"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; enum MenuOptions { GAME_SETTINGS, @@ -539,10 +540,7 @@ export default class MenuUiHandler extends MessageUiHandler { window.open(discordUrl, "_self"); return true; } else { - Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } + pokerogueApi.unlinkDiscord().then(_isSuccess => { updateUserInfo().then(() => this.scene.reset(true, true)); }); return true; @@ -560,10 +558,7 @@ export default class MenuUiHandler extends MessageUiHandler { window.open(googleUrl, "_self"); return true; } else { - Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } + pokerogueApi.unlinkGoogle().then(_isSuccess => { updateUserInfo().then(() => this.scene.reset(true, true)); }); return true; @@ -612,11 +607,7 @@ export default class MenuUiHandler extends MessageUiHandler { success = true; const doLogout = () => { ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { - if (!res.ok) { - console.error(`Log out failed (${res.status}: ${res.statusText})`); - } - Utils.removeCookie(Utils.sessionIdKey); + buttonActions: [], fadeOut: () => pokerogueApi.account.logout().then(() => { updateUserInfo().then(() => this.scene.reset(true, true)); }) }); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 3f89ebe415f..a0358b5ca8c 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -1,6 +1,6 @@ import BattleScene from "../battle-scene"; import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "../modifier/modifier-type"; -import { getPokeballAtlasKey, PokeballType } from "../data/pokeball"; +import { getPokeballAtlasKey } from "#app/data/pokeball"; import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; import { Mode } from "./ui"; @@ -15,6 +15,7 @@ import i18next from "i18next"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { IntegerHolder } from "./../utils"; import Phaser from "phaser"; +import type { PokeballType } from "#enums/pokeball"; export const SHOP_OPTIONS_ROW_LIMIT = 7; const SINGLE_SHOP_ROW_YOFFSET = 12; diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index 6c58d32c515..d9c4200ea9b 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -3,7 +3,7 @@ import { TextStyle, addTextObject } from "./text"; import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; import Move, { MoveCategory } from "../data/move"; -import { Type } from "../data/type"; +import { Type } from "#enums/type"; import i18next from "i18next"; export interface MoveInfoOverlaySettings { diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index b568f8b71de..cdb1c9024c5 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -369,9 +369,9 @@ export default class MysteryEncounterUiHandler extends UiHandler { let text: string | null; if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) { // Options with special requirements that are met are automatically colored green - text = getEncounterText(this.scene, label, TextStyle.SUMMARY_GREEN); + text = getEncounterText(this.scene, label, TextStyle.ME_OPTION_SPECIAL); } else { - text = getEncounterText(this.scene, label, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW); + text = getEncounterText(this.scene, label, optionDialogue.style ? optionDialogue.style : TextStyle.ME_OPTION_DEFAULT); } if (text) { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index a26aa572ef3..bd3561dd0b4 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -8,7 +8,7 @@ import * as Utils from "#app/utils"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; import { getGenderColor, getGenderSymbol } from "#app/data/gender"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 146d70522fd..494855d20fa 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -1,7 +1,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import BattleScene from "#app/battle-scene"; import { Gender } from "#app/data/gender"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import * as Utils from "#app/utils"; import { TextStyle, addTextObject } from "#app/ui/text"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 5b11aff43b1..e0d432265a3 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -3,7 +3,7 @@ import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import BattleScene from "../battle-scene"; import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; import { getNatureName } from "../data/nature"; -import { Type } from "../data/type"; +import { Type } from "#enums/type"; import Pokemon from "../field/pokemon"; import i18next from "i18next"; import { DexAttr, DexEntry, StarterDataEntry } from "../system/game-data"; @@ -313,6 +313,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0)); this.pokemonShinyNewIcon.setVisible(!!newShinyOrVariant); + } else if ((caughtAttr & DexAttr.NON_SHINY) === BigInt(0) && ((caughtAttr & DexAttr.SHINY) === DexAttr.SHINY)) { //If the player has *only* caught any shiny variant of this species, not a non-shiny + this.pokemonShinyNewIcon.setVisible(true); + this.pokemonShinyNewIcon.setText("(+)"); + this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); } else { this.pokemonShinyNewIcon.setVisible(false); } diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index 2c35ff8ee7f..892f78bd1ba 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -1,9 +1,9 @@ import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; import { ModalConfig } from "./modal-ui-handler"; -import * as Utils from "../utils"; import { Mode } from "./ui"; import { TextStyle, addTextObject } from "./text"; import i18next from "i18next"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; interface LanguageSetting { @@ -110,27 +110,20 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { if (this.inputs[1].text !== this.inputs[2].text) { return onFail(i18next.t("menu:passwordNotMatchingConfirmPassword")); } - Utils.apiPost("account/register", `username=${encodeURIComponent(this.inputs[0].text)}&password=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded") - .then(response => response.text()) - .then(response => { - if (!response) { - Utils.apiPost("account/login", `username=${encodeURIComponent(this.inputs[0].text)}&password=${encodeURIComponent(this.inputs[1].text)}`, "application/x-www-form-urlencoded") - .then(response => { - if (!response.ok) { - return response.text(); - } - return response.json(); - }) - .then(response => { - if (response.hasOwnProperty("token")) { - Utils.setCookie(Utils.sessionIdKey, response.token); + const [ usernameInput, passwordInput ] = this.inputs; + pokerogueApi.account.register({ username: usernameInput.text, password: passwordInput.text }) + .then(registerError => { + if (!registerError) { + pokerogueApi.account.login({ username: usernameInput.text, password: passwordInput.text }) + .then(loginError => { + if (!loginError) { originalRegistrationAction && originalRegistrationAction(); } else { - onFail(response); + onFail(loginError); } }); } else { - onFail(response); + onFail(registerError); } }); }; diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 4975f05b8a3..071690aee54 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -15,7 +15,8 @@ import { TrainerVariant } from "../field/trainer"; import { Challenges } from "#enums/challenges"; import { getLuckString, getLuckTextTint } from "../modifier/modifier-type"; import RoundRectangle from "phaser3-rex-plugins/plugins/roundrectangle"; -import { Type, getTypeRgb } from "../data/type"; +import { getTypeRgb } from "#app/data/type"; +import { Type } from "#enums/type"; import { TypeColor, TypeShadow } from "#app/enums/color"; import { getNatureStatMultiplier, getNatureName } from "../data/nature"; import { getVariantTint } from "#app/data/variant"; @@ -117,6 +118,7 @@ export default class RunInfoUiHandler extends UiHandler { this.runResultContainer = this.scene.add.container(0, 24); const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65); runResultWindow.setOrigin(0, 0); + runResultWindow.setName("Run_Result_Window"); this.runResultContainer.add(runResultWindow); if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { this.parseRunResult(); @@ -253,8 +255,6 @@ export default class RunInfoUiHandler extends UiHandler { * Mystery Encounters contain sprites associated with MEs + the title of the specific ME. */ private parseRunStatus() { - const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 }); - const enemyContainer = this.scene.add.container(0, 0); this.runResultContainer.add(enemyContainer); if (this.runInfo.battleType === BattleType.WILD) { @@ -270,7 +270,7 @@ export default class RunInfoUiHandler extends UiHandler { const pokeball = this.scene.add.sprite(0, 0, "pb"); pokeball.setFrame(getPokeballAtlasKey(p.pokeball)); pokeball.setScale(0.5); - pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); + pokeball.setPosition(58 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); enemyContainer.add(pokeball); }); const trainerObj = this.runInfo.trainer.toTrainer(this.scene); @@ -285,7 +285,7 @@ export default class RunInfoUiHandler extends UiHandler { const descContainer = this.scene.add.container(0, 0); const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); descContainer.add(textBox); - descContainer.setPosition(52, 29); + descContainer.setPosition(55, 32); this.runResultContainer.add(descContainer); } else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) { const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim"); @@ -302,7 +302,17 @@ export default class RunInfoUiHandler extends UiHandler { this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]); } - this.runResultContainer.add(runStatusText); + const runResultWindow = this.runResultContainer.getByName("Run_Result_Window") as Phaser.GameObjects.Image; + const windowCenterX = runResultWindow.getTopCenter().x; + const windowBottomY = runResultWindow.getBottomCenter().y; + + const runStatusText = addTextObject(this.scene, windowCenterX, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, TextStyle.WINDOW, { fontSize : "60px", lineSpacing: 0.1 }); + runStatusText.setOrigin(0.5, 0); + + const currentBiomeText = addTextObject(this.scene, windowCenterX, windowBottomY - 5, `${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize: "60px" }); + currentBiomeText.setOrigin(0.5, 1); + + this.runResultContainer.add([ runStatusText, currentBiomeText ]); this.runContainer.add(this.runResultContainer); } @@ -386,12 +396,12 @@ export default class RunInfoUiHandler extends UiHandler { tObjSprite.setPosition(-9, -3); tObjPartnerSprite.setScale(0.55); doubleContainer.add([ tObjSprite, tObjPartnerSprite ]); - doubleContainer.setPosition(28, 40); + doubleContainer.setPosition(28, 34); } enemyContainer.add(doubleContainer); } else { - const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65; - const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ]; + const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.55; + const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 30, 32 ]; tObjSprite.setScale(scale, scale); tObjSprite.setPosition(position[0], position[1]); enemyContainer.add(tObjSprite); @@ -517,7 +527,8 @@ export default class RunInfoUiHandler extends UiHandler { const runTime = Utils.getPlayTimeString(this.runInfo.playTime); runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false); const runMoney = Utils.formatMoney(this.scene.moneyFormat, this.runInfo.money); - runInfoText.appendText(`[color=${getTextColor(TextStyle.MONEY)}]${i18next.t("battleScene:moneyOwned", { formattedMoney : runMoney })}[/color]`); + const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, this.scene.uiTheme); + runInfoText.appendText(`[color=${moneyTextColor}]${i18next.t("battleScene:moneyOwned", { formattedMoney : runMoney })}[/color]`); runInfoText.setPosition(7, 70); runInfoTextContainer.add(runInfoText); // Luck diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 83219e1ef5a..cbc93887810 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -1,8 +1,7 @@ import BattleScene from "#app/battle-scene"; -import { hasTouchscreen, isMobile } from "#app/touch-controls"; import { TextStyle, addTextObject } from "#app/ui/text"; import { Mode } from "#app/ui/ui"; -import UiHandler from "#app/ui/ui-handler"; +import MessageUiHandler from "#app/ui/message-ui-handler"; import { addWindow } from "#app/ui/ui-theme"; import { ScrollBar } from "#app/ui/scroll-bar"; import { Button } from "#enums/buttons"; @@ -15,9 +14,10 @@ import i18next from "i18next"; /** * Abstract class for handling UI elements related to settings. */ -export default class AbstractSettingsUiHandler extends UiHandler { +export default class AbstractSettingsUiHandler extends MessageUiHandler { private settingsContainer: Phaser.GameObjects.Container; private optionsContainer: Phaser.GameObjects.Container; + private messageBoxContainer: Phaser.GameObjects.Container; private navigationContainer: NavigationMenu; private scrollCursor: number; @@ -135,6 +135,23 @@ export default class AbstractSettingsUiHandler extends UiHandler { this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); this.scrollBar.setTotalRows(this.settings.length); + // Two-lines message box + this.messageBoxContainer = this.scene.add.container(0, this.scene.scaledCanvas.height); + this.messageBoxContainer.setName("settings-message-box"); + this.messageBoxContainer.setVisible(false); + + const settingsMessageBox = addWindow(this.scene, 0, -1, this.scene.scaledCanvas.width - 2, 48); + settingsMessageBox.setOrigin(0, 1); + this.messageBoxContainer.add(settingsMessageBox); + + const messageText = addTextObject(this.scene, 8, -40, "", TextStyle.WINDOW, { maxLines: 2 }); + messageText.setWordWrapWidth(this.scene.game.canvas.width - 60); + messageText.setName("settings-message"); + messageText.setOrigin(0, 0); + + this.messageBoxContainer.add(messageText); + this.message = messageText; + this.settingsContainer.add(this.optionsBg); this.settingsContainer.add(this.scrollBar); this.settingsContainer.add(this.navigationContainer); @@ -144,6 +161,7 @@ export default class AbstractSettingsUiHandler extends UiHandler { this.settingsContainer.add(iconCancel); this.settingsContainer.add(actionText); this.settingsContainer.add(cancelText); + this.settingsContainer.add(this.messageBoxContainer); ui.add(this.settingsContainer); @@ -326,18 +344,16 @@ export default class AbstractSettingsUiHandler extends UiHandler { /** * Set the option cursor to the specified position. * - * @param settingIndex - The index of the setting. + * @param settingIndex - The index of the setting or -1 to change the current setting * @param cursor - The cursor position to set. * @param save - Whether to save the setting to local storage. * @returns `true` if the option cursor was set successfully. */ setOptionCursor(settingIndex: number, cursor: number, save?: boolean): boolean { - const setting = this.settings[settingIndex]; - - if (setting.key === SettingKeys.Touch_Controls && cursor && hasTouchscreen() && isMobile()) { - this.getUi().playError(); - return false; + if (settingIndex === -1) { + settingIndex = this.cursor + this.scrollCursor; } + const setting = this.settings[settingIndex]; const lastCursor = this.optionCursors[settingIndex]; @@ -352,9 +368,33 @@ export default class AbstractSettingsUiHandler extends UiHandler { newValueLabel.setShadowColor(this.getTextColor(TextStyle.SETTINGS_SELECTED, true)); if (save) { - this.scene.gameData.saveSetting(setting.key, cursor); - if (this.reloadSettings.includes(setting)) { - this.reloadRequired = true; + const saveSetting = () => { + this.scene.gameData.saveSetting(setting.key, cursor); + if (setting.requireReload) { + this.reloadRequired = true; + } + }; + + // For settings that ask for confirmation, display confirmation message and a Yes/No prompt before saving the setting + if (setting.options[cursor].needConfirmation) { + const confirmUpdateSetting = () => { + this.scene.ui.revertMode(); + this.showText(""); + saveSetting(); + }; + const cancelUpdateSetting = () => { + this.scene.ui.revertMode(); + this.showText(""); + // Put the cursor back to its previous position without saving or asking for confirmation again + this.setOptionCursor(settingIndex, lastCursor, false); + }; + + const confirmationMessage = setting.options[cursor].confirmationMessage ?? i18next.t("settings:defaultConfirmMessage"); + this.scene.ui.showText(confirmationMessage, null, () => { + this.scene.ui.setOverlayMode(Mode.CONFIRM, confirmUpdateSetting, cancelUpdateSetting, null, null, 1, 750); + }); + } else { + saveSetting(); } } @@ -421,4 +461,9 @@ export default class AbstractSettingsUiHandler extends UiHandler { } this.cursorObj = null; } + + override showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + this.messageBoxContainer.setVisible(!!text?.length); + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 3a29f9431e7..691e339eafc 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -10,13 +10,13 @@ import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { allMoves } from "#app/data/move"; -import { Nature, getNatureName } from "#app/data/nature"; +import { getNatureName } from "#app/data/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; import PokemonSpecies, { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; -import { Type } from "#app/data/type"; +import { Type } from "#enums/type"; import { GameModes } from "#app/game-mode"; import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "#app/system/game-data"; import { Tutorial, handleTutorial } from "#app/tutorial"; @@ -39,7 +39,6 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Button } from "#enums/buttons"; import { EggSourceType } from "#enums/egg-source-types"; -import AwaitableUiHandler from "#app/ui/awaitable-ui-handler"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; import { StarterContainer } from "#app/ui/starter-container"; import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; @@ -50,6 +49,8 @@ import { TitlePhase } from "#app/phases/title-phase"; import { Abilities } from "#enums/abilities"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; import { BooleanHolder, capitalizeString, fixedInt, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, randIntRange, rgbHexToRgba, toReadableString } from "#app/utils"; +import type { Nature } from "#enums/nature"; +import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -242,6 +243,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokemonEggMoveContainers: Phaser.GameObjects.Container[]; private pokemonEggMoveBgs: Phaser.GameObjects.NineSlice[]; private pokemonEggMoveLabels: Phaser.GameObjects.Text[]; + private pokemonCandyContainer: Phaser.GameObjects.Container; private pokemonCandyIcon: Phaser.GameObjects.Sprite; private pokemonCandyDarknessOverlay: Phaser.GameObjects.Sprite; private pokemonCandyOverlayIcon: Phaser.GameObjects.Sprite; @@ -686,31 +688,36 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonLuckText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonLuckText); - this.pokemonCandyIcon = this.scene.add.sprite(4.5, 18, "candy"); + // Candy icon and count + this.pokemonCandyContainer = this.scene.add.container(4.5, 18); + + this.pokemonCandyIcon = this.scene.add.sprite(0, 0, "candy"); this.pokemonCandyIcon.setScale(0.5); this.pokemonCandyIcon.setOrigin(0, 0); - this.starterSelectContainer.add(this.pokemonCandyIcon); + this.pokemonCandyContainer.add(this.pokemonCandyIcon); - this.pokemonFormText = addTextObject(this.scene, 6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" }); - this.pokemonFormText.setOrigin(0, 0); - this.starterSelectContainer.add(this.pokemonFormText); - - this.pokemonCandyOverlayIcon = this.scene.add.sprite(4.5, 18, "candy_overlay"); + this.pokemonCandyOverlayIcon = this.scene.add.sprite(0, 0, "candy_overlay"); this.pokemonCandyOverlayIcon.setScale(0.5); this.pokemonCandyOverlayIcon.setOrigin(0, 0); - this.starterSelectContainer.add(this.pokemonCandyOverlayIcon); + this.pokemonCandyContainer.add(this.pokemonCandyOverlayIcon); - this.pokemonCandyDarknessOverlay = this.scene.add.sprite(4.5, 18, "candy"); + this.pokemonCandyDarknessOverlay = this.scene.add.sprite(0, 0, "candy"); this.pokemonCandyDarknessOverlay.setScale(0.5); this.pokemonCandyDarknessOverlay.setOrigin(0, 0); this.pokemonCandyDarknessOverlay.setTint(0x000000); this.pokemonCandyDarknessOverlay.setAlpha(0.50); - this.pokemonCandyDarknessOverlay.setInteractive(new Phaser.Geom.Rectangle(0, 0, 16, 16), Phaser.Geom.Rectangle.Contains); - this.starterSelectContainer.add(this.pokemonCandyDarknessOverlay); + this.pokemonCandyContainer.add(this.pokemonCandyDarknessOverlay); - this.pokemonCandyCountText = addTextObject(this.scene, 14, 18, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }); + this.pokemonCandyCountText = addTextObject(this.scene, 9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }); this.pokemonCandyCountText.setOrigin(0, 0); - this.starterSelectContainer.add(this.pokemonCandyCountText); + this.pokemonCandyContainer.add(this.pokemonCandyCountText); + + this.pokemonCandyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 20), Phaser.Geom.Rectangle.Contains); + this.starterSelectContainer.add(this.pokemonCandyContainer); + + this.pokemonFormText = addTextObject(this.scene, 6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" }); + this.pokemonFormText.setOrigin(0, 0); + this.starterSelectContainer.add(this.pokemonFormText); this.pokemonCaughtHatchedContainer = this.scene.add.container(2, 25); this.pokemonCaughtHatchedContainer.setScale(0.5); @@ -1054,15 +1061,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } - showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer, moveToTop?: boolean) { super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); - if (text?.indexOf("\n") === -1) { - this.starterSelectMessageBox.setSize(318, 28); - this.message.setY(-22); + const singleLine = text?.indexOf("\n") === -1; + + this.starterSelectMessageBox.setSize(318, singleLine ? 28 : 42); + + if (moveToTop) { + this.starterSelectMessageBox.setOrigin(0, 0); + this.starterSelectMessageBoxContainer.setY(0); + this.message.setY(4); } else { - this.starterSelectMessageBox.setSize(318, 42); - this.message.setY(-37); + this.starterSelectMessageBoxContainer.setY(this.scene.game.canvas.height / 6); + this.starterSelectMessageBox.setOrigin(0, 1); + this.message.setY(singleLine ? -22 : -37); } this.starterSelectMessageBoxContainer.setVisible(!!text?.length); @@ -1455,7 +1468,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newCost = this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); - if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < 6) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party + if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party options = [ { label: i18next.t("starterSelectUiHandler:addToParty"), @@ -1796,8 +1809,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { options.push({ label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { - if ((this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) - && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) { + if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) { + if (this.scene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + // Egg list full, show error message at the top of the screen and abort + this.showText(i18next.t("egg:tooManyEggs"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), 2000, false, undefined, true); + return false; + } if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { starterData.candyCount -= sameSpeciesEggCost; } @@ -2822,10 +2839,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonShinyIcon.setY(135); this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); [ - this.pokemonCandyIcon, - this.pokemonCandyOverlayIcon, - this.pokemonCandyDarknessOverlay, - this.pokemonCandyCountText, + this.pokemonCandyContainer, this.pokemonHatchedIcon, this.pokemonHatchedCountText ].map(c => c.setVisible(false)); @@ -2834,31 +2848,26 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonCaughtHatchedContainer.setY(25); this.pokemonShinyIcon.setY(117); this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); - this.pokemonCandyIcon.setVisible(true); this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); - this.pokemonCandyOverlayIcon.setVisible(true); - this.pokemonCandyDarknessOverlay.setVisible(true); this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`); - this.pokemonCandyCountText.setVisible(true); + this.pokemonCandyContainer.setVisible(true); this.pokemonFormText.setY(42); this.pokemonHatchedIcon.setVisible(true); this.pokemonHatchedCountText.setVisible(true); const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); const candyCropY = 16 - (16 * (currentFriendship / friendshipCap)); - - if (this.pokemonCandyDarknessOverlay.visible) { - this.pokemonCandyDarknessOverlay.on("pointerover", () => { - this.scene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true); - this.activeTooltip = "CANDY"; - }); - this.pokemonCandyDarknessOverlay.on("pointerout", () => { - this.scene.ui.hideTooltip(); - this.activeTooltip = undefined; - }); - } - this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY); + + this.pokemonCandyContainer.on("pointerover", () => { + this.scene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true); + this.activeTooltip = "CANDY"; + }); + this.pokemonCandyContainer.on("pointerout", () => { + this.scene.ui.hideTooltip(); + this.activeTooltip = undefined; + }); + } @@ -2934,10 +2943,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonPassiveLabelText.setVisible(false); this.pokemonNatureLabelText.setVisible(false); this.pokemonCaughtHatchedContainer.setVisible(false); - this.pokemonCandyIcon.setVisible(false); - this.pokemonCandyOverlayIcon.setVisible(false); - this.pokemonCandyDarknessOverlay.setVisible(false); - this.pokemonCandyCountText.setVisible(false); + this.pokemonCandyContainer.setVisible(false); this.pokemonFormText.setVisible(false); const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, true, true); @@ -2971,10 +2977,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonPassiveLabelText.setVisible(false); this.pokemonNatureLabelText.setVisible(false); this.pokemonCaughtHatchedContainer.setVisible(false); - this.pokemonCandyIcon.setVisible(false); - this.pokemonCandyOverlayIcon.setVisible(false); - this.pokemonCandyDarknessOverlay.setVisible(false); - this.pokemonCandyCountText.setVisible(false); + this.pokemonCandyContainer.setVisible(false); this.pokemonFormText.setVisible(false); this.setSpeciesDetails(species!, { // TODO: is this bang correct? @@ -3005,7 +3008,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { || !isNullOrUndefined(formIndex) || !isNullOrUndefined(shiny) || !isNullOrUndefined(variant); if (this.activeTooltip === "CANDY") { - if (this.lastSpecies) { + if (this.lastSpecies && this.pokemonCandyContainer.visible) { const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); } else { @@ -3230,6 +3233,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonPassiveLockedIcon.setVisible(!isUnlocked); this.pokemonPassiveLockedIcon.setPosition(iconPosition.x, iconPosition.y); + } else if (this.activeTooltip === "PASSIVE") { + // No passive and passive tooltip is active > hide it + this.scene.ui.hideTooltip(); } this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true, false, this.scene.uiTheme)); @@ -3568,9 +3574,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }, cancel, null, null, 19); }); } else { - const handler = this.scene.ui.getHandler() as AwaitableUiHandler; - handler.tutorialActive = true; - this.scene.ui.showText(i18next.t("starterSelectUiHandler:invalidParty"), null, () => this.scene.ui.showText("", 0, () => handler.tutorialActive = false), null, true); + this.tutorialActive = true; + this.showText(i18next.t("starterSelectUiHandler:invalidParty"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), undefined, true); } return true; } diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 9a61dd0f688..63ef6155fbc 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -5,16 +5,17 @@ import * as Utils from "#app/utils"; import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; -import { Type, getTypeRgb } from "#app/data/type"; +import { getTypeRgb } from "#app/data/type"; +import { Type } from "#enums/type"; import { TextStyle, addBBCodeTextObject, addTextObject, getBBCodeFrag } from "#app/ui/text"; import Move, { MoveCategory } from "#app/data/move"; import { getPokeballAtlasKey } from "#app/data/pokeball"; import { getGenderColor, getGenderSymbol } from "#app/data/gender"; import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import { StatusEffect } from "#app/data/status-effect"; +import { StatusEffect } from "#enums/status-effect"; import { getBiomeName } from "#app/data/balance/biomes"; -import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature"; +import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { loggedInUser } from "#app/account"; import { Variant, getVariantTint } from "#app/data/variant"; import { Button } from "#enums/buttons"; @@ -23,6 +24,7 @@ import i18next from "i18next"; import { modifierSortFunc } from "#app/modifier/modifier"; import { PlayerGender } from "#enums/player-gender"; import { Stat, PERMANENT_STATS, getStatKey } from "#enums/stat"; +import { Nature } from "#enums/nature"; enum Page { PROFILE, @@ -184,7 +186,7 @@ export default class SummaryUiHandler extends UiHandler { this.candyShadow.setTint(0x000000); this.candyShadow.setAlpha(0.50); this.candyShadow.setScale(0.8); - this.candyShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 16, 16), Phaser.Geom.Rectangle.Contains); + this.candyShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 16), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.candyShadow); this.candyCountText = addTextObject(this.scene, 20, -146, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); @@ -203,7 +205,7 @@ export default class SummaryUiHandler extends UiHandler { this.friendshipShadow.setTint(0x000000); this.friendshipShadow.setAlpha(0.50); this.friendshipShadow.setScale(0.8); - this.friendshipShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 16, 16), Phaser.Geom.Rectangle.Contains); + this.friendshipShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 50, 16), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.friendshipShadow); this.friendshipText = addTextObject(this.scene, 20, -66, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); @@ -319,8 +321,12 @@ export default class SummaryUiHandler extends UiHandler { this.numberText.setText(Utils.padInt(this.pokemon.species.speciesId, 4)); this.numberText.setColor(this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD)); this.numberText.setShadowColor(this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true)); - - this.pokemonSprite.play(this.pokemon.getSpriteKey(true)); + const spriteKey = this.pokemon.getSpriteKey(true); + try { + this.pokemonSprite.play(spriteKey); + } catch (err: unknown) { + console.error(`Failed to play animation for ${spriteKey}`, err); + } this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())); this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index ecc15e5985e..249ae7b8b01 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -13,9 +13,11 @@ import { SubstituteTag } from "#app/data/battler-tags"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; export default class TargetSelectUiHandler extends UiHandler { - private fieldIndex: integer; + private fieldIndex: number; private move: Moves; private targetSelectCallback: TargetSelectCallback; + private cursor0: number; // associated with BattlerIndex.PLAYER + private cursor1: number; // associated with BattlerIndex.PLAYER_2 private isMultipleTargets: boolean = false; private targets: BattlerIndex[]; @@ -42,8 +44,9 @@ export default class TargetSelectUiHandler extends UiHandler { this.fieldIndex = args[0] as integer; this.move = args[1] as Moves; this.targetSelectCallback = args[2] as TargetSelectCallback; + const user = this.scene.getPlayerField()[this.fieldIndex]; - const moveTargets = getMoveTargets(this.scene.getPlayerField()[this.fieldIndex], this.move); + const moveTargets = getMoveTargets(user, this.move); this.targets = moveTargets.targets; this.isMultipleTargets = moveTargets.multiple ?? false; @@ -53,11 +56,29 @@ export default class TargetSelectUiHandler extends UiHandler { this.enemyModifiers = this.scene.getModifierBar(true); - this.setCursor(this.targets.includes(this.cursor) ? this.cursor : this.targets[0]); - + if (this.fieldIndex === BattlerIndex.PLAYER) { + this.resetCursor(this.cursor0, user); + } else if (this.fieldIndex === BattlerIndex.PLAYER_2) { + this.resetCursor(this.cursor1, user); + } return true; } + /** + * Determines what value to assign the main cursor based on the previous turn's target or the user's status + * @param cursorN the cursor associated with the user's field index + * @param user the Pokemon using the move + */ + resetCursor(cursorN: number, user: Pokemon): void { + if (!Utils.isNullOrUndefined(cursorN)) { + if ([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ].includes(cursorN) || user.battleSummonData.waveTurnCount === 1) { + // Reset cursor on the first turn of a fight or if an ally was targeted last turn + cursorN = -1; + } + } + this.setCursor(this.targets.includes(cursorN) ? cursorN : this.targets[0]); + } + processInput(button: Button): boolean { const ui = this.getUi(); @@ -67,6 +88,15 @@ export default class TargetSelectUiHandler extends UiHandler { const targetIndexes: BattlerIndex[] = this.isMultipleTargets ? this.targets : [ this.cursor ]; this.targetSelectCallback(button === Button.ACTION ? targetIndexes : []); success = true; + if (this.fieldIndex === BattlerIndex.PLAYER) { + if (Utils.isNullOrUndefined(this.cursor0) || this.cursor0 !== this.cursor) { + this.cursor0 = this.cursor; + } + } else if (this.fieldIndex === BattlerIndex.PLAYER_2) { + if (Utils.isNullOrUndefined(this.cursor1) || this.cursor1 !== this.cursor) { + this.cursor1 = this.cursor; + } + } } else if (this.isMultipleTargets) { success = false; } else { @@ -152,7 +182,6 @@ export default class TargetSelectUiHandler extends UiHandler { yoyo: true })); }); - return ret; } @@ -184,7 +213,6 @@ export default class TargetSelectUiHandler extends UiHandler { } clear() { - this.cursor = -1; super.clear(); this.eraseCursor(); } diff --git a/src/ui/text.ts b/src/ui/text.ts index 069aa8680fc..17ae02be9ef 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -22,7 +22,8 @@ export enum TextStyle { SUMMARY_GOLD, SUMMARY_GRAY, SUMMARY_GREEN, - MONEY, + MONEY, // Money default styling (pale yellow) + MONEY_WINDOW, // Money displayed in Windows (needs different colors based on theme) STATS_LABEL, STATS_VALUE, SETTINGS_VALUE, @@ -38,7 +39,9 @@ export enum TextStyle { MOVE_PP_EMPTY, SMALLER_WINDOW_ALT, BGM_BAR, - PERFECT_IV + PERFECT_IV, + ME_OPTION_DEFAULT, // Default style for choices in ME + ME_OPTION_SPECIAL, // Style for choices with special requirements in ME } export interface TextStyleOptions { @@ -139,6 +142,8 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty case TextStyle.SUMMARY_GREEN: case TextStyle.WINDOW: case TextStyle.WINDOW_ALT: + case TextStyle.ME_OPTION_DEFAULT: + case TextStyle.ME_OPTION_SPECIAL: shadowXpos = 3; shadowYpos = 3; break; @@ -177,6 +182,7 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty break; case TextStyle.BATTLE_INFO: case TextStyle.MONEY: + case TextStyle.MONEY_WINDOW: case TextStyle.TOOLTIP_TITLE: styleOptions.fontSize = defaultFontSize - 24; shadowXpos = 3.5; @@ -238,13 +244,22 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui * - "red text" with TextStyle.SUMMARY_RED applied * @param content string with styling that need to be applied for BBCodeTextObject * @param primaryStyle Primary style is required in order to escape BBCode styling properly. - * @param uiTheme + * @param uiTheme the {@linkcode UiTheme} to get TextStyle for + * @param forWindow set to `true` if the text is to be displayed in a window ({@linkcode BattleScene.addWindow}) + * it will replace all instances of the default MONEY TextStyle by {@linkcode TextStyle.MONEY_WINDOW} */ -export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string { +export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme, forWindow?: boolean): string { // Apply primary styling before anything else let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]"; const primaryStyleString = [ ...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))! ][0]; + /* For money text displayed in game windows, we can't use the default {@linkcode TextStyle.MONEY} + * or it will look wrong in legacy mode because of the different window background color + * So, for text to be displayed in windows replace all "@[MONEY]" with "@[MONEY_WINDOW]" */ + if (forWindow) { + text = text.replace(/@\[MONEY\]/g, (_substring: string) => "@[MONEY_WINDOW]"); + } + // Set custom colors text = text.replace(/@\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => { return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle], uiTheme) + "[/color][/shadow]" + primaryStyleString; @@ -310,7 +325,12 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui return !shadow ? "#f89890" : "#984038"; case TextStyle.SUMMARY_GOLD: case TextStyle.MONEY: - return !shadow ? "#e8e8a8" : "#a0a060"; + return !shadow ? "#e8e8a8" : "#a0a060"; // Pale Yellow/Gold + case TextStyle.MONEY_WINDOW: + if (isLegacyTheme) { + return !shadow ? "#f8b050" : "#c07800"; // Gold + } + return !shadow ? "#e8e8a8" : "#a0a060"; // Pale Yellow/Gold case TextStyle.SETTINGS_LOCKED: case TextStyle.SUMMARY_GRAY: return !shadow ? "#a0a0a0" : "#636363"; @@ -332,6 +352,13 @@ export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: Ui return !shadow ? "#484848" : "#d0d0c8"; case TextStyle.BGM_BAR: return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.ME_OPTION_DEFAULT: + return !shadow ? "#f8f8f8" : "#6b5a73"; // White + case TextStyle.ME_OPTION_SPECIAL: + if (isLegacyTheme) { + return !shadow ? "#f8b050" : "#c07800"; // Gold + } + return !shadow ? "#78c850" : "#306850"; // Green } } diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index 3bfba71ef08..aec80f049c9 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -7,6 +7,7 @@ import { getSplashMessages } from "../data/splash-messages"; import i18next from "i18next"; import { TimedEventDisplay } from "#app/timed-event-manager"; import { version } from "../../package.json"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; export default class TitleUiHandler extends OptionSelectUiHandler { /** If the stats can not be retrieved, use this fallback value */ @@ -78,12 +79,13 @@ export default class TitleUiHandler extends OptionSelectUiHandler { } updateTitleStats(): void { - Utils.apiFetch("game/titlestats") - .then(request => request.json()) + pokerogueApi.getGameTitleStats() .then(stats => { - this.playerCountLabel.setText(`${stats.playerCount} ${i18next.t("menu:playersOnline")}`); - if (this.splashMessage === "splashMessages:battlesWon") { - this.splashMessageText.setText(i18next.t(this.splashMessage, { count: stats.battleCount })); + if (stats) { + this.playerCountLabel.setText(`${stats.playerCount} ${i18next.t("menu:playersOnline")}`); + if (this.splashMessage === "splashMessages:battlesWon") { + this.splashMessageText.setText(i18next.t(this.splashMessage, { count: stats.battleCount })); + } } }) .catch(err => { diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 63cd48ab1cd..fc8fa94c848 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -391,6 +391,7 @@ export default class UI extends Phaser.GameObjects.Container { this.tooltipContent.y = title ? 16 : 4; this.tooltipBg.width = Math.min(Math.max(this.tooltipTitle.displayWidth, this.tooltipContent.displayWidth) + 12, 838); this.tooltipBg.height = (title ? 31 : 19) + 10.5 * (wrappedContent.split("\n").length - 1); + this.tooltipTitle.x = this.tooltipBg.width / 2; } hideTooltip(): void { @@ -400,12 +401,34 @@ export default class UI extends Phaser.GameObjects.Container { update(): void { if (this.tooltipContainer.visible) { - const xReverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.x >= this.scene.game.canvas.width - this.tooltipBg.width * 6 - 12; - const yReverse = this.scene.game.input.mousePointer && this.scene.game.input.mousePointer.y >= this.scene.game.canvas.height - this.tooltipBg.height * 6 - 12; - this.tooltipContainer.setPosition( - !xReverse ? this.scene.game.input.mousePointer!.x / 6 + 2 : this.scene.game.input.mousePointer!.x / 6 - this.tooltipBg.width - 2, - !yReverse ? this.scene.game.input.mousePointer!.y / 6 + 2 : this.scene.game.input.mousePointer!.y / 6 - this.tooltipBg.height - 2, - ); + const isTouch = (this.scene as BattleScene).inputMethod === "touch"; + const pointerX = this.scene.game.input.activePointer.x; + const pointerY = this.scene.game.input.activePointer.y; + const tooltipWidth = this.tooltipBg.width; + const tooltipHeight = this.tooltipBg.height; + const padding = 2; + + // Default placement is top left corner of the screen on mobile. Otherwise below the cursor, to the right + let x = isTouch ? padding : pointerX / 6 + padding; + let y = isTouch ? padding : pointerY / 6 + padding; + + if (isTouch) { + // If we are in the top left quadrant on mobile, move the tooltip to the top right corner + if (pointerX <= this.scene.game.canvas.width / 2 && pointerY <= this.scene.game.canvas.height / 2) { + x = this.scene.game.canvas.width / 6 - tooltipWidth - padding; + } + } else { + // If the tooltip would go offscreen on the right, or is close to it, move to the left of the cursor + if (x + tooltipWidth + padding > this.scene.game.canvas.width / 6) { + x = Math.max(padding, pointerX / 6 - tooltipWidth - padding); + } + // If the tooltip would go offscreen at the bottom, or is close to it, move above the cursor + if (y + tooltipHeight + padding > this.scene.game.canvas.height / 6) { + y = Math.max(padding, pointerY / 6 - tooltipHeight - padding); + } + } + + this.tooltipContainer.setPosition(x, y); } } diff --git a/src/utils.ts b/src/utils.ts index 8b779f32730..be0aec84ecd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import { MoneyFormat } from "#enums/money-format"; import { Moves } from "#enums/moves"; import i18next from "i18next"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; export type nil = null | undefined; @@ -258,9 +259,16 @@ export const isLocal = ( /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(window.location.hostname)) && window.location.port !== "") || window.location.hostname === ""; +/** + * @deprecated Refer to [pokerogue-api.ts](./plugins/api/pokerogue-api.ts) instead + */ export const localServerUrl = import.meta.env.VITE_SERVER_URL ?? `http://${window.location.hostname}:${window.location.port + 1}`; -// Set the server URL based on whether it's local or not +/** + * Set the server URL based on whether it's local or not + * + * @deprecated Refer to [pokerogue-api.ts](./plugins/api/pokerogue-api.ts) instead + */ export const apiUrl = localServerUrl ?? "https://api.pokerogue.net"; // used to disable api calls when isLocal is true and a server is not found export let isLocalServerConnected = true; @@ -307,48 +315,14 @@ export function getCookie(cName: string): string { * with a GET request to verify if a server is running, * sets isLocalServerConnected based on results */ -export function localPing() { +export async function localPing() { if (isLocal) { - apiFetch("game/titlestats") - .then(resolved => isLocalServerConnected = true, - rejected => isLocalServerConnected = false - ); + const titleStats = await pokerogueApi.getGameTitleStats(); + isLocalServerConnected = !!titleStats; + console.log("isLocalServerConnected:", isLocalServerConnected); } } -export function apiFetch(path: string, authed: boolean = false): Promise { - return (isLocal && isLocalServerConnected) || !isLocal ? new Promise((resolve, reject) => { - const request = {}; - if (authed) { - const sId = getCookie(sessionIdKey); - if (sId) { - request["headers"] = { "Authorization": sId }; - } - } - fetch(`${apiUrl}/${path}`, request) - .then(response => resolve(response)) - .catch(err => reject(err)); - }) : new Promise(() => {}); -} - -export function apiPost(path: string, data?: any, contentType: string = "application/json", authed: boolean = false): Promise { - return (isLocal && isLocalServerConnected) || !isLocal ? new Promise((resolve, reject) => { - const headers = { - "Accept": contentType, - "Content-Type": contentType, - }; - if (authed) { - const sId = getCookie(sessionIdKey); - if (sId) { - headers["Authorization"] = sId; - } - } - fetch(`${apiUrl}/${path}`, { method: "POST", headers: headers, body: data }) - .then(response => resolve(response)) - .catch(err => reject(err)); - }) : new Promise(() => {}); -} - /** Alias for the constructor of a class */ export type Constructor = new(...args: unknown[]) => T;