├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Readme.md ├── data ├── moves.json └── pokemon.json ├── index.html ├── package.json ├── server ├── config │ └── properties.js ├── index.js ├── models │ ├── Credential.js │ ├── Pokemon.js │ └── Species.js ├── routes │ └── pokemonRoutes.js └── utils │ ├── expressUtils.js │ └── pokemonUtils.js ├── systemjs.config.js ├── tsconfig.json ├── typings.json └── webapp ├── components ├── app.component.ts ├── login-form.component.ts ├── login.component.ts ├── pokemon-species.component.ts ├── pokemon-stats.component.ts ├── pokemon-table.component.ts └── settings.component.ts ├── img ├── candy-icon.png ├── checkbox-empty-icon.png ├── checkbox-filled-icon.png ├── heart-icon.png ├── shield-icon.png ├── star-icon.png ├── strength-icon.png └── sword-icon.png ├── main.ts ├── models ├── move-data.model.ts ├── move.model.ts ├── pokemon-data.model.ts ├── pokemon-table-stat.model.ts ├── pokemon.model.ts ├── sort-order.model.ts ├── sort-type.model.ts ├── species.model.ts └── user-login.model.ts ├── pipes └── prepend-zeros.pipe.ts ├── services ├── export.service.ts ├── pokemon.service.ts ├── properties.service.ts ├── settings.service.ts ├── sort.service.ts └── utils.service.ts ├── styles ├── global.css ├── login-form.component.css ├── login.component.css ├── pokemon-species.component.css ├── pokemon-stats.component.css ├── pokemon-table.component.css └── settings.component.css └── templates ├── app.component.html ├── login-form.component.html ├── login.component.html ├── pokemon-species.component.html ├── pokemon-stats.component.html ├── pokemon-table.component.html └── settings.component.html /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | Readme.md 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ##ignore node modules 2 | node_modules 3 | 4 | ##ignore compiled js 5 | webapp/js/* 6 | 7 | ##ignore typings 8 | typings 9 | 10 | ##ignore vscode files 11 | .vscode 12 | 13 | ##ignore private properties 14 | server/config/private.json -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.3 2 | 3 | ENV PATH=./node_modules/.bin:$PATH 4 | 5 | RUN mkdir /app 6 | WORKDIR /app 7 | COPY ./ ./ 8 | 9 | RUN npm install && typings install 10 | 11 | EXPOSE 8080 8008 12 | CMD npm start 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Eric Carlton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | This repository is no longer maintained 2 | ======================================= 3 | It's been fun, but dealing with potential shadow bans and constant api updates just aren't what I want to be doing with my free time any more. Thanks to everyone who used / cloned / forked / helped update this app. If anyone has a fork that they're still maintaining, let me know and I'll direct visitors to you. 4 | 5 | Pokemon Go Reader 6 | ================= 7 | Requests information about Pokemon currently in inventory from Niantic and displays stats for each Pokemon in a card or table format. 8 | 9 | Screenshots 10 | ----------- 11 | ![Login Screen](http://i.imgur.com/O85jdWt.png "Login Screen") 12 | 13 | ![Pokemon Stats Cards](http://i.imgur.com/qbnBb4W.png "Pokemon Stats Cards") 14 | 15 | ![Pokemon Stats Table](http://i.imgur.com/4G1ZUfI.png "Pokemon Stats Table") 16 | 17 | ![Species Cards](http://i.imgur.com/YDTtQlB.png "Species Cards") 18 | 19 | Dependencies 20 | ------------ 21 | 1. Install [NodeJS](https://nodejs.org/) - Node version used in development is 6.3.1 22 | 2. Navigate to project root, open a terminal, and run: 23 | 24 | `npm install` 25 | 26 | to install dependencies 27 | 28 | Hashing 29 | ------- 30 | In order to use this application, users **must** [purchase a hashing key](https://talk.pogodev.org/d/51-api-hashing-service-by-pokefarmer). The cheapest option (150 RPM) should be more than sufficient. Once you've got your key, simply create a file in server/config called `private.json`. Contents of the file should be formatted as follows: 31 | 32 | ``` 33 | { 34 | "hashingKey":"" 35 | } 36 | ``` 37 | 38 | That's it! After that's done, you should be good to go! 39 | 40 | Building/Running 41 | ---------------- 42 | * To run both the Node server and the webapp, open a terminal in the project root and run: 43 | 44 | `npm start` 45 | 46 | This will start the Node server on the port specified in server/config/properties.js by the server.port property. It will also transpile the webapp and start a http-server instance on [http://localhost:8080](http://localhost:8080). 47 | 48 | * To run only the Node server, open a terminal in the project root and run: 49 | 50 | `npm run server` 51 | 52 | This will start the Node server on the port specified in server/config/properties.js by the server.port property. 53 | 54 | * To run only the webapp, open a terminal in the project root and run: 55 | 56 | `npm run webapp` 57 | 58 | This will transpile the webapp and start a http-server instance on [http://localhost:8080](http://localhost:8080). 59 | 60 | * To run only the webapp in development mode, open a terminal in the project root and run: 61 | 62 | `npm run webapp-dev` 63 | 64 | This will transpile the webapp, watch for front end changes and re-transpile when they are detected, and start a lite-server instance on [http://localhost:3000](http://localhost:3000). 65 | 66 | Acknowlegements 67 | --------------- 68 | * This project wouldn't be possible without [cyraxx](https://github.com/cyraxx) and the other developers over at [PogoBuf](https://github.com/cyraxx/pogobuf). They're doing all the heavy lifting. A huge thanks to everyone who's working on that project! 69 | 70 | * Using [justinleewells](https://github.com/justinleewells) [level-to-cp](https://github.com/justinleewells/pogo-optimizer/blob/master/data/game/level-to-cpm.json) map to calculate Pokemon level. 71 | 72 | * This project wouldn't currently be working without the help of [Devin Falgoust](https://github.com/DevinFalgoust). Thanks for the various bugfixes and keeping Pokemon and move data up to date! 73 | 74 | Warning 75 | ------- 76 | 77 | Pulling data from Niantic by anything other than the Pokemon Go app violates the Pokemon Go Terms of Use: 78 | 79 | >USER RIGHTS AND RESTRICTIONS. These Terms grant permission to you, in your individual capacity, to use the content of Service made available to you for personal, noncommercial home use only. In no instance may you: 80 | > 81 | > ... 82 | > 83 | > (ii) Modify, or create derivative works based on, the content; 84 | > 85 | >(iii) Use, or facilitate the use of, any unauthorized third-party software (e.g. bots, mods, hacks, and scripts) to modify or automate operation within the Service whether for yourself or for a third party. 86 | > 87 | >... 88 | > 89 | >(vi) Decompose, disassemble, or reverse engineer any part of any Service, or otherwise use a Service for any purpose other than those provided for by us and in conjunction with the operations of the Service; 90 | 91 | I haven't heard of any instances of account bans for reasons other than botting or mapping, but keep in mind there is a risk when using this tool or others like it. 92 | 93 | Known Issues 94 | ------------ 95 | * IE and Safari don't seem to like some of the CSS being used. I develop with Chrome, so I reccommend that you use Chrome to run this app. I can guarantee that anything on master works in Chrome. 96 | 97 | FAQs 98 | ----- 99 | * **I've recently pulled and the new version doesn't work!** 100 | 101 | I may have added a dependency or updated a minimum version for an old dependency. Try running `npm install`. If that doesn't fix your problem, please [open an issue](https://github.com/Eric-Carlton/PokemonGoReader/issues). 102 | 103 | * **Help! I'm seeing errors after an `npm start` on my Linux system! I've followed all of the setup instructions and it just isn't working!** 104 | 105 | The postinstall script doesn't seem to run automatically on some Linux systems. Try running `npm run postinstall` after `npm install`. See [issue #1](https://github.com/Eric-Carlton/PokemonGoReader/issues/1) for a full description. 106 | 107 | * **I can't seem to log in with my Google account, even though I know I'm using the correct email and password. What gives?** 108 | 109 | If you use a Google account and have two factor authentication enabled, you will need to [generate an app password](https://security.google.com/settings/security/apppasswords) and use that to log in. 110 | 111 | * **I want to allow other computers on my local network to access the tool from my computer. Is that possible?** 112 | 113 | It sure is! You do, however, need to make some configuration changes. 114 | 115 | 1. Open up webapp/services/properties.service.ts and find the apiHost property. In a normal configuration, that is set to `'//' + window.location.hostname + ':8008'`. You'll need to change this to `'//:8008'`. 116 | 2. Next, you'll need to open up port 8080 and port 8008 on your computer's firewall so that requests can make it through to the webserver and the Node server. 117 | 3. Restart the webapp portion ( or the whole thing if you're running with `npm start` ) and you should be set! 118 | 119 | * **Running locally is fine and dandy, but what about if I want my friends to be able to access the tool *outside* of my local network?** 120 | 121 | Well, dear traveller, you are in for a treat! This is definitely possible, but takes a bit of setup to get going. Be warned, this opens your computer up to external traffic, which may not always be friendly! Proceed with caution! Here is a list of steps to get you started. I haven't actually done this, so take it with a grain of salt, but this should theoretically work. 122 | 123 | 1. You're going to need to forward ports 8080 and 8008 in your router's settings. If you don't know how to do this, you can likely Google '<your_router_model> port forwarding' and figure it out. 124 | 2. Next, you'll need to open up your firewall on ports 8080 and 8008, if you haven't done this already. 125 | 3. Change the apiHost property in webapp/services/properties.service.ts to `'//:8008'`. Your public IP can be obtained from an IP checker [like this one](https://whatismyipaddress.com). 126 | 4. You should now be able to access the tool from outside your local network by navigating to :8080. Your public IP is likely IPv6, which requires some [fancy address bar formatting](http://www.cyberciti.biz/faq/how-can-ipv6-address-used-with-webbrowser/) to get your browser to navigate to it. 127 | 128 | * **I'm trying to host this project on Amazon/Heroku/Azure, but I keep getting 403's on login** 129 | 130 | Niantic has banned [a lot of major VPS providers](https://www.reddit.com/r/pokemongodev/comments/4vhygk/_/?st=irmo101z&sh=8816b817), so you'll have to search around to find one that isn't banned yet. 131 | 132 | * **I'm facing an issue that hasn't been addressed here, or I'd like to add a feature request** 133 | 134 | [Open an issue](https://github.com/Eric-Carlton/PokemonGoReader/issues). I try to respond to errors within 24 hrs - no guarantees - and I'll generally prioritize feature requests weekly. If an issue is marked 'free to take', that means I don't plan on getting to it any time soon, but anyone is welcome to fork my repo and submit a pull request! 135 | 136 | IV Explanation 137 | -------------- 138 | ![IV Explanation Panel 1](http://i.imgur.com/eM1JBYM.png "IV Explanation Panel 1") 139 | 140 | ![IV Explanation Panel 2](http://i.imgur.com/K0bgtif.png "IV Explanation Panel 2") 141 | 142 | ![IV Explanation Panel 3](http://i.imgur.com/pd1hC2C.png "IV Explanation Panel 3") 143 | 144 | ![IV Explanation Panel 4](http://i.imgur.com/7Ca1qE9.png "IV Explanation Panel 4") -------------------------------------------------------------------------------- /data/moves.json: -------------------------------------------------------------------------------- 1 | [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 2 | }, {}, {}, { 3 | "Id": 13, 4 | "Name": "Wrap", 5 | "Speed": "Charge", 6 | "Type": "Normal", 7 | "Power": 60, 8 | "DurationMs": 2900 9 | }, { 10 | "Id": 14, 11 | "Name": "Hyper Beam", 12 | "Speed": "Charge", 13 | "Type": "Normal", 14 | "Power": 150, 15 | "DurationMs": 3800 16 | }, {}, { 17 | "Id": 16, 18 | "Name": "Dark Pulse", 19 | "Speed": "Charge", 20 | "Type": "Dark", 21 | "Power": 80, 22 | "DurationMs": 3000 23 | }, {}, { 24 | "Id": 18, 25 | "Name": "Sludge", 26 | "Speed": "Charge", 27 | "Type": "Poison", 28 | "Power": 50, 29 | "DurationMs": 2100 30 | }, {}, { 31 | "Id": 20, 32 | "Name": "Vice Grip", 33 | "Speed": "Charge", 34 | "Type": "Normal", 35 | "Power": 35, 36 | "DurationMs": 1900 37 | }, { 38 | "Id": 21, 39 | "Name": "Flame Wheel", 40 | "Speed": "Charge", 41 | "Type": "Fire", 42 | "Power": 60, 43 | "DurationMs": 2700 44 | }, { 45 | "Id": 22, 46 | "Name": "Megahorn", 47 | "Speed": "Charge", 48 | "Type": "Bug", 49 | "Power": 90, 50 | "DurationMs": 2200 51 | }, {}, { 52 | "Id": 24, 53 | "Name": "Flamethrower", 54 | "Speed": "Charge", 55 | "Type": "Fire", 56 | "Power": 70, 57 | "DurationMs": 2200 58 | }, {}, { 59 | "Id": 26, 60 | "Name": "Dig", 61 | "Speed": "Charge", 62 | "Type": "Ground", 63 | "Power": 100, 64 | "DurationMs": 4700 65 | }, {}, { 66 | "Id": 28, 67 | "Name": "Cross Chop", 68 | "Speed": "Charge", 69 | "Type": "Fighting", 70 | "Power": 50, 71 | "DurationMs": 1500 72 | }, {}, { 73 | "Id": 30, 74 | "Name": "Psybeam", 75 | "Speed": "Charge", 76 | "Type": "Psychic", 77 | "Power": 70, 78 | "DurationMs": 3200 79 | }, { 80 | "Id": 31, 81 | "Name": "Earthquake", 82 | "Speed": "Charge", 83 | "Type": "Ground", 84 | "Power": 120, 85 | "DurationMs": 3600 86 | }, { 87 | "Id": 32, 88 | "Name": "Stone Edge", 89 | "Speed": "Charge", 90 | "Type": "Rock", 91 | "Power": 100, 92 | "DurationMs": 2300 93 | }, { 94 | "Id": 33, 95 | "Name": "Ice Punch", 96 | "Speed": "Charge", 97 | "Type": "Ice", 98 | "Power": 50, 99 | "DurationMs": 1900 100 | }, { 101 | "Id": 34, 102 | "Name": "Heart Stamp", 103 | "Speed": "Charge", 104 | "Type": "Psychic", 105 | "Power": 40, 106 | "DurationMs": 1900 107 | }, { 108 | "Id": 35, 109 | "Name": "Discharge", 110 | "Speed": "Charge", 111 | "Type": "Electric", 112 | "Power": 65, 113 | "DurationMs": 2500 114 | }, { 115 | "Id": 36, 116 | "Name": "Flash Cannon", 117 | "Speed": "Charge", 118 | "Type": "Steel", 119 | "Power": 100, 120 | "DurationMs": 2700 121 | }, {}, { 122 | "Id": 38, 123 | "Name": "Drill Peck", 124 | "Speed": "Charge", 125 | "Type": "Flying", 126 | "Power": 60, 127 | "DurationMs": 2300 128 | }, { 129 | "Id": 39, 130 | "Name": "Ice Beam", 131 | "Speed": "Charge", 132 | "Type": "Ice", 133 | "Power": 90, 134 | "DurationMs": 3300 135 | }, { 136 | "Id": 40, 137 | "Name": "Blizzard", 138 | "Speed": "Charge", 139 | "Type": "Ice", 140 | "Power": 130, 141 | "DurationMs": 3100 142 | }, {}, { 143 | "Id": 42, 144 | "Name": "Heat Wave", 145 | "Speed": "Charge", 146 | "Type": "Fire", 147 | "Power": 95, 148 | "DurationMs": 3000 149 | }, {}, {}, { 150 | "Id": 45, 151 | "Name": "Aerial Ace", 152 | "Speed": "Charge", 153 | "Type": "Flying", 154 | "Power": 55, 155 | "DurationMs": 2400 156 | }, { 157 | "Id": 46, 158 | "Name": "Drill Run", 159 | "Speed": "Charge", 160 | "Type": "Ground", 161 | "Power": 80, 162 | "DurationMs": 2800 163 | }, { 164 | "Id": 47, 165 | "Name": "Petal Blizzard", 166 | "Speed": "Charge", 167 | "Type": "Grass", 168 | "Power": 110, 169 | "DurationMs": 2600 170 | }, { 171 | "Id": 48, 172 | "Name": "Mega Drain", 173 | "Speed": "Charge", 174 | "Type": "Grass", 175 | "Power": 25, 176 | "DurationMs": 2600 177 | }, { 178 | "Id": 49, 179 | "Name": "Bug Buzz", 180 | "Speed": "Charge", 181 | "Type": "Bug", 182 | "Power": 90, 183 | "DurationMs": 3700 184 | }, { 185 | "Id": 50, 186 | "Name": "Poison Fang", 187 | "Speed": "Charge", 188 | "Type": "Poison", 189 | "Power": 35, 190 | "DurationMs": 1700 191 | }, { 192 | "Id": 51, 193 | "Name": "Night Slash", 194 | "Speed": "Charge", 195 | "Type": "Dark", 196 | "Power": 50, 197 | "DurationMs": 2200 198 | }, {}, { 199 | "Id": 53, 200 | "Name": "Bubble Beam", 201 | "Speed": "Charge", 202 | "Type": "Water", 203 | "Power": 45, 204 | "DurationMs": 1900 205 | }, { 206 | "Id": 54, 207 | "Name": "Submission", 208 | "Speed": "Charge", 209 | "Type": "Fighting", 210 | "Power": 60, 211 | "DurationMs": 2200 212 | }, {}, { 213 | "Id": 56, 214 | "Name": "Low Sweep", 215 | "Speed": "Charge", 216 | "Type": "Fighting", 217 | "Power": 40, 218 | "DurationMs": 1900 219 | }, { 220 | "Id": 57, 221 | "Name": "Aqua Jet", 222 | "Speed": "Charge", 223 | "Type": "Water", 224 | "Power": 45, 225 | "DurationMs": 2600 226 | }, { 227 | "Id": 58, 228 | "Name": "Aqua Tail", 229 | "Speed": "Charge", 230 | "Type": "Water", 231 | "Power": 50, 232 | "DurationMs": 1900 233 | }, { 234 | "Id": 59, 235 | "Name": "Seed Bomb", 236 | "Speed": "Charge", 237 | "Type": "Grass", 238 | "Power": 55, 239 | "DurationMs": 2100 240 | }, { 241 | "Id": 60, 242 | "Name": "Psyshock", 243 | "Speed": "Charge", 244 | "Type": "Psychic", 245 | "Power": 65, 246 | "DurationMs": 2700 247 | }, {}, { 248 | "Id": 62, 249 | "Name": "Ancient Power", 250 | "Speed": "Charge", 251 | "Type": "Rock", 252 | "Power": 70, 253 | "DurationMs": 3500 254 | }, { 255 | "Id": 63, 256 | "Name": "Rock Tomb", 257 | "Speed": "Charge", 258 | "Type": "Rock", 259 | "Power": 70, 260 | "DurationMs": 3200 261 | }, { 262 | "Id": 64, 263 | "Name": "Rock Slide", 264 | "Speed": "Charge", 265 | "Type": "Rock", 266 | "Power": 80, 267 | "DurationMs": 2700 268 | }, { 269 | "Id": 65, 270 | "Name": "Power Gem", 271 | "Speed": "Charge", 272 | "Type": "Rock", 273 | "Power": 80, 274 | "DurationMs": 2900 275 | }, { 276 | "Id": 66, 277 | "Name": "Shadow Sneak", 278 | "Speed": "Charge", 279 | "Type": "Ghost", 280 | "Power": 50, 281 | "DurationMs": 2900 282 | }, { 283 | "Id": 67, 284 | "Name": "Shadow Punch", 285 | "Speed": "Charge", 286 | "Type": "Ghost", 287 | "Power": 40, 288 | "DurationMs": 1700 289 | }, {}, { 290 | "Id": 69, 291 | "Name": "Ominous Wind", 292 | "Speed": "Charge", 293 | "Type": "Ghost", 294 | "Power": 50, 295 | "DurationMs": 2300 296 | }, { 297 | "Id": 70, 298 | "Name": "Shadow Ball", 299 | "Speed": "Charge", 300 | "Type": "Ghost", 301 | "Power": 100, 302 | "DurationMs": 3000 303 | }, {}, { 304 | "Id": 72, 305 | "Name": "Magnet Bomb", 306 | "Speed": "Charge", 307 | "Type": "Steel", 308 | "Power": 70, 309 | "DurationMs": 2800 310 | }, {}, { 311 | "Id": 74, 312 | "Name": "Iron Head", 313 | "Speed": "Charge", 314 | "Type": "Steel", 315 | "Power": 60, 316 | "DurationMs": 1900 317 | }, { 318 | "Id": 75, 319 | "Name": "Parabolic Charge", 320 | "Speed": "Charge", 321 | "Type": "Electric", 322 | "Power": 25, 323 | "DurationMs": 2800 324 | }, {}, { 325 | "Id": 77, 326 | "Name": "Thunder Punch", 327 | "Speed": "Charge", 328 | "Type": "Electric", 329 | "Power": 45, 330 | "DurationMs": 1800 331 | }, { 332 | "Id": 78, 333 | "Name": "Thunder", 334 | "Speed": "Charge", 335 | "Type": "Electric", 336 | "Power": 100, 337 | "DurationMs": 2400 338 | }, { 339 | "Id": 79, 340 | "Name": "Thunderbolt", 341 | "Speed": "Charge", 342 | "Type": "Electric", 343 | "Power": 80, 344 | "DurationMs": 2500 345 | }, { 346 | "Id": 80, 347 | "Name": "Twister", 348 | "Speed": "Charge", 349 | "Type": "Dragon", 350 | "Power": 45, 351 | "DurationMs": 2800 352 | }, {}, { 353 | "Id": 82, 354 | "Name": "Dragon Pulse", 355 | "Speed": "Charge", 356 | "Type": "Dragon", 357 | "Power": 90, 358 | "DurationMs": 3600 359 | }, { 360 | "Id": 83, 361 | "Name": "Dragon Claw", 362 | "Speed": "Charge", 363 | "Type": "Dragon", 364 | "Power": 50, 365 | "DurationMs": 1700 366 | }, { 367 | "Id": 84, 368 | "Name": "Disarming Voice", 369 | "Speed": "Charge", 370 | "Type": "Fairy", 371 | "Power": 70, 372 | "DurationMs": 3900 373 | }, { 374 | "Id": 85, 375 | "Name": "Draining Kiss", 376 | "Speed": "Charge", 377 | "Type": "Fairy", 378 | "Power": 60, 379 | "DurationMs": 2600 380 | }, { 381 | "Id": 86, 382 | "Name": "Dazzling Gleam", 383 | "Speed": "Charge", 384 | "Type": "Fairy", 385 | "Power": 100, 386 | "DurationMs": 3500 387 | }, { 388 | "Id": 87, 389 | "Name": "Moonblast", 390 | "Speed": "Charge", 391 | "Type": "Fairy", 392 | "Power": 130, 393 | "DurationMs": 3900 394 | }, { 395 | "Id": 88, 396 | "Name": "Play Rough", 397 | "Speed": "Charge", 398 | "Type": "Fairy", 399 | "Power": 90, 400 | "DurationMs": 2900 401 | }, { 402 | "Id": 89, 403 | "Name": "Cross Poison", 404 | "Speed": "Charge", 405 | "Type": "Poison", 406 | "Power": 40, 407 | "DurationMs": 1500 408 | }, { 409 | "Id": 90, 410 | "Name": "Sludge Bomb", 411 | "Speed": "Charge", 412 | "Type": "Poison", 413 | "Power": 80, 414 | "DurationMs": 2300 415 | }, { 416 | "Id": 91, 417 | "Name": "Sludge Wave", 418 | "Speed": "Charge", 419 | "Type": "Poison", 420 | "Power": 110, 421 | "DurationMs": 3200 422 | }, { 423 | "Id": 92, 424 | "Name": "Gunk Shot", 425 | "Speed": "Charge", 426 | "Type": "Poison", 427 | "Power": 130, 428 | "DurationMs": 3100 429 | }, {}, { 430 | "Id": 94, 431 | "Name": "Bone Club", 432 | "Speed": "Charge", 433 | "Type": "Ground", 434 | "Power": 40, 435 | "DurationMs": 1600 436 | }, { 437 | "Id": 95, 438 | "Name": "Bulldoze", 439 | "Speed": "Charge", 440 | "Type": "Ground", 441 | "Power": 80, 442 | "DurationMs": 3500 443 | }, { 444 | "Id": 96, 445 | "Name": "Mud Bomb", 446 | "Speed": "Charge", 447 | "Type": "Ground", 448 | "Power": 55, 449 | "DurationMs": 2300 450 | }, {}, {}, { 451 | "Id": 99, 452 | "Name": "Signal Beam", 453 | "Speed": "Charge", 454 | "Type": "Bug", 455 | "Power": 75, 456 | "DurationMs": 2900 457 | }, { 458 | "Id": 100, 459 | "Name": "X Scissor", 460 | "Speed": "Charge", 461 | "Type": "Bug", 462 | "Power": 45, 463 | "DurationMs": 1600 464 | }, { 465 | "Id": 101, 466 | "Name": "Flame Charge", 467 | "Speed": "Charge", 468 | "Type": "Fire", 469 | "Power": 70, 470 | "DurationMs": 3800 471 | }, { 472 | "Id": 102, 473 | "Name": "Flame Burst", 474 | "Speed": "Charge", 475 | "Type": "Fire", 476 | "Power": 70, 477 | "DurationMs": 2600 478 | }, { 479 | "Id": 103, 480 | "Name": "Fire Blast", 481 | "Speed": "Charge", 482 | "Type": "Fire", 483 | "Power": 140, 484 | "DurationMs": 4200 485 | }, { 486 | "Id": 104, 487 | "Name": "Brine", 488 | "Speed": "Charge", 489 | "Type": "Water", 490 | "Power": 60, 491 | "DurationMs": 2300 492 | }, { 493 | "Id": 105, 494 | "Name": "Water Pulse", 495 | "Speed": "Charge", 496 | "Type": "Water", 497 | "Power": 70, 498 | "DurationMs": 3200 499 | }, { 500 | "Id": 106, 501 | "Name": "Scald", 502 | "Speed": "Charge", 503 | "Type": "Water", 504 | "Power": 80, 505 | "DurationMs": 3700 506 | }, { 507 | "Id": 107, 508 | "Name": "Hydro Pump", 509 | "Speed": "Charge", 510 | "Type": "Water", 511 | "Power": 130, 512 | "DurationMs": 3300 513 | }, { 514 | "Id": 108, 515 | "Name": "Psychic", 516 | "Speed": "Charge", 517 | "Type": "Psychic", 518 | "Power": 100, 519 | "DurationMs": 2800 520 | }, { 521 | "Id": 109, 522 | "Name": "Psystrike", 523 | "Speed": "Charge", 524 | "Type": "Psychic", 525 | "Power": 100, 526 | "DurationMs": 4400 527 | }, {}, { 528 | "Id": 111, 529 | "Name": "Icy Wind", 530 | "Speed": "Charge", 531 | "Type": "Ice", 532 | "Power": 60, 533 | "DurationMs": 3300 534 | }, {}, {}, { 535 | "Id": 114, 536 | "Name": "Giga Drain", 537 | "Speed": "Charge", 538 | "Type": "Grass", 539 | "Power": 50, 540 | "DurationMs": 3900 541 | }, { 542 | "Id": 115, 543 | "Name": "Fire Punch", 544 | "Speed": "Charge", 545 | "Type": "Fire", 546 | "Power": 55, 547 | "DurationMs": 2200 548 | }, { 549 | "Id": 116, 550 | "Name": "Solar Beam", 551 | "Speed": "Charge", 552 | "Type": "Grass", 553 | "Power": 180, 554 | "DurationMs": 4900 555 | }, { 556 | "Id": 117, 557 | "Name": "Leaf Blade", 558 | "Speed": "Charge", 559 | "Type": "Grass", 560 | "Power": 70, 561 | "DurationMs": 2400 562 | }, { 563 | "Id": 118, 564 | "Name": "Power Whip", 565 | "Speed": "Charge", 566 | "Type": "Grass", 567 | "Power": 90, 568 | "DurationMs": 2600 569 | }, {}, {}, { 570 | "Id": 121, 571 | "Name": "Air Cutter", 572 | "Speed": "Charge", 573 | "Type": "Flying", 574 | "Power": 60, 575 | "DurationMs": 2700 576 | }, { 577 | "Id": 122, 578 | "Name": "Hurricane", 579 | "Speed": "Charge", 580 | "Type": "Flying", 581 | "Power": 110, 582 | "DurationMs": 2700 583 | }, { 584 | "Id": 123, 585 | "Name": "Brick Break", 586 | "Speed": "Charge", 587 | "Type": "Fighting", 588 | "Power": 40, 589 | "DurationMs": 1600 590 | }, {}, { 591 | "Id": 125, 592 | "Name": "Swift", 593 | "Speed": "Charge", 594 | "Type": "Normal", 595 | "Power": 60, 596 | "DurationMs": 2800 597 | }, { 598 | "Id": 126, 599 | "Name": "Horn Attack", 600 | "Speed": "Charge", 601 | "Type": "Normal", 602 | "Power": 40, 603 | "DurationMs": 1850 604 | }, { 605 | "Id": 127, 606 | "Name": "Stomp", 607 | "Speed": "Charge", 608 | "Type": "Normal", 609 | "Power": 55, 610 | "DurationMs": 1700 611 | }, {}, { 612 | "Id": 129, 613 | "Name": "Hyper Fang", 614 | "Speed": "Charge", 615 | "Type": "Normal", 616 | "Power": 80, 617 | "DurationMs": 2500 618 | }, {}, { 619 | "Id": 131, 620 | "Name": "Body Slam", 621 | "Speed": "Charge", 622 | "Type": "Normal", 623 | "Power": 50, 624 | "DurationMs": 1900 625 | }, { 626 | "Id": 132, 627 | "Name": "Rest", 628 | "Speed": "Charge", 629 | "Type": "Normal", 630 | "Power": 50, 631 | "DurationMs": 1900 632 | }, { 633 | "Id": 133, 634 | "Name": "Struggle", 635 | "Speed": "Charge", 636 | "Type": "Normal", 637 | "Power": 35, 638 | "DurationMs": 2200 639 | }, { 640 | "Id": 134, 641 | "Name": "Scald Blastoise", 642 | "Speed": "Charge", 643 | "Type": "Water", 644 | "Power": 50, 645 | "DurationMs": 4700 646 | }, { 647 | "Id": 135, 648 | "Name": "Hydro Pump Blastoise", 649 | "Speed": "Charge", 650 | "Type": "Water", 651 | "Power": 90, 652 | "DurationMs": 4500 653 | }, { 654 | "Id": 136, 655 | "Name": "Wrap Green", 656 | "Speed": "Charge", 657 | "Type": "Normal", 658 | "Power": 25, 659 | "DurationMs": 2900 660 | }, { 661 | "Id": 137, 662 | "Name": "Wrap Pink", 663 | "Speed": "Charge", 664 | "Type": "Normal", 665 | "Power": 25, 666 | "DurationMs": 2900 667 | }, {}, {}, { 668 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 669 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 670 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 671 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 672 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 673 | }, {}, {}, {}, {}, {}, {}, {}, {}, {}, { 674 | "Id": 200, 675 | "Name": "Fury Cutter", 676 | "Speed": "Fast", 677 | "Type": "Bug", 678 | "Power": 3, 679 | "DurationMs": 400 680 | }, { 681 | "Id": 201, 682 | "Name": "Bug Bite", 683 | "Speed": "Fast", 684 | "Type": "Bug", 685 | "Power": 5, 686 | "DurationMs": 500 687 | }, { 688 | "Id": 202, 689 | "Name": "Bite", 690 | "Speed": "Fast", 691 | "Type": "Dark", 692 | "Power": 6, 693 | "DurationMs": 500 694 | }, { 695 | "Id": 203, 696 | "Name": "Sucker Punch", 697 | "Speed": "Fast", 698 | "Type": "Dark", 699 | "Power": 7, 700 | "DurationMs": 700 701 | }, { 702 | "Id": 204, 703 | "Name": "Dragon Breath", 704 | "Speed": "Fast", 705 | "Type": "Dragon", 706 | "Power": 6, 707 | "DurationMs": 500 708 | }, { 709 | "Id": 205, 710 | "Name": "Thunder Shock", 711 | "Speed": "Fast", 712 | "Type": "Electric", 713 | "Power": 5, 714 | "DurationMs": 600 715 | }, { 716 | "Id": 206, 717 | "Name": "Spark", 718 | "Speed": "Fast", 719 | "Type": "Electric", 720 | "Power": 6, 721 | "DurationMs": 700 722 | }, { 723 | "Id": 207, 724 | "Name": "Low Kick", 725 | "Speed": "Fast", 726 | "Type": "Fighting", 727 | "Power": 6, 728 | "DurationMs": 600 729 | }, { 730 | "Id": 208, 731 | "Name": "Karate Chop", 732 | "Speed": "Fast", 733 | "Type": "Fighting", 734 | "Power": 8, 735 | "DurationMs": 800 736 | }, { 737 | "Id": 209, 738 | "Name": "Ember", 739 | "Speed": "Fast", 740 | "Type": "Fire", 741 | "Power": 10, 742 | "DurationMs": 1000 743 | }, { 744 | "Id": 210, 745 | "Name": "Wing Attack", 746 | "Speed": "Fast", 747 | "Type": "Flying", 748 | "Power": 8, 749 | "DurationMs": 800 750 | }, { 751 | "Id": 211, 752 | "Name": "Peck", 753 | "Speed": "Fast", 754 | "Type": "Flying", 755 | "Power": 10, 756 | "DurationMs": 1000 757 | }, { 758 | "Id": 212, 759 | "Name": "Lick", 760 | "Speed": "Fast", 761 | "Type": "Ghost", 762 | "Power": 5, 763 | "DurationMs": 500 764 | }, { 765 | "Id": 213, 766 | "Name": "Shadow Claw", 767 | "Speed": "Fast", 768 | "Type": "Ghost", 769 | "Power": 9, 770 | "DurationMs": 700 771 | }, { 772 | "Id": 214, 773 | "Name": "Vine Whip", 774 | "Speed": "Fast", 775 | "Type": "Grass", 776 | "Power": 7, 777 | "DurationMs": 600 778 | }, { 779 | "Id": 215, 780 | "Name": "Razor Leaf", 781 | "Speed": "Fast", 782 | "Type": "Grass", 783 | "Power": 13, 784 | "DurationMs": 1000 785 | }, { 786 | "Id": 216, 787 | "Name": "Mud Shot", 788 | "Speed": "Fast", 789 | "Type": "Ground", 790 | "Power": 5, 791 | "DurationMs": 600 792 | }, { 793 | "Id": 217, 794 | "Name": "Ice Shard", 795 | "Speed": "Fast", 796 | "Type": "Ice", 797 | "Power": 12, 798 | "DurationMs": 1200 799 | }, { 800 | "Id": 218, 801 | "Name": "Frost Breath", 802 | "Speed": "Fast", 803 | "Type": "Ice", 804 | "Power": 10, 805 | "DurationMs": 900 806 | }, { 807 | "Id": 219, 808 | "Name": "Quick Attack", 809 | "Speed": "Fast", 810 | "Type": "Normal", 811 | "Power": 8, 812 | "DurationMs": 800 813 | }, { 814 | "Id": 220, 815 | "Name": "Scratch", 816 | "Speed": "Fast", 817 | "Type": "Normal", 818 | "Power": 6, 819 | "DurationMs": 500 820 | }, { 821 | "Id": 221, 822 | "Name": "Tackle", 823 | "Speed": "Fast", 824 | "Type": "Normal", 825 | "Power": 5, 826 | "DurationMs": 500 827 | }, { 828 | "Id": 222, 829 | "Name": "Pound", 830 | "Speed": "Fast", 831 | "Type": "Normal", 832 | "Power": 7, 833 | "DurationMs": 600 834 | }, { 835 | "Id": 223, 836 | "Name": "Cut", 837 | "Speed": "Fast", 838 | "Type": "Normal", 839 | "Power": 5, 840 | "DurationMs": 500 841 | }, { 842 | "Id": 224, 843 | "Name": "Poison Jab", 844 | "Speed": "Fast", 845 | "Type": "Poison", 846 | "Power": 10, 847 | "DurationMs": 800 848 | }, { 849 | "Id": 225, 850 | "Name": "Acid", 851 | "Speed": "Fast", 852 | "Type": "Poison", 853 | "Power": 9, 854 | "DurationMs": 800 855 | }, { 856 | "Id": 226, 857 | "Name": "Psycho Cut", 858 | "Speed": "Fast", 859 | "Type": "Psychic", 860 | "Power": 5, 861 | "DurationMs": 600 862 | }, { 863 | "Id": 227, 864 | "Name": "Rock Throw", 865 | "Speed": "Fast", 866 | "Type": "Rock", 867 | "Power": 12, 868 | "DurationMs": 900 869 | }, { 870 | "Id": 228, 871 | "Name": "Metal Claw", 872 | "Speed": "Fast", 873 | "Type": "Steel", 874 | "Power": 8, 875 | "DurationMs": 700 876 | }, { 877 | "Id": 229, 878 | "Name": "Bullet Punch", 879 | "Speed": "Fast", 880 | "Type": "Steel", 881 | "Power": 9, 882 | "DurationMs": 900 883 | }, { 884 | "Id": 230, 885 | "Name": "Water Gun", 886 | "Speed": "Fast", 887 | "Type": "Water", 888 | "Power": 5, 889 | "DurationMs": 500 890 | }, { 891 | "Id": 231, 892 | "Name": "Splash", 893 | "Speed": "Fast", 894 | "Type": "Water", 895 | "Power": 0, 896 | "DurationMs": 1730 897 | }, { 898 | "Id": 232, 899 | "Name": "Water Gun Blastoise", 900 | "Speed": "Fast", 901 | "Type": "Water", 902 | "Power": 10, 903 | "DurationMs": 1000 904 | }, { 905 | "Id": 233, 906 | "Name": "Mud Slap", 907 | "Speed": "Fast", 908 | "Type": "Ground", 909 | "Power": 15, 910 | "DurationMs": 1400 911 | }, { 912 | "Id": 234, 913 | "Name": "Zen Headbutt", 914 | "Speed": "Fast", 915 | "Type": "Psychic", 916 | "Power": 12, 917 | "DurationMs": 1100 918 | }, { 919 | "Id": 235, 920 | "Name": "Confusion", 921 | "Speed": "Fast", 922 | "Type": "Psychic", 923 | "Power": 20, 924 | "DurationMs": 1600 925 | }, { 926 | "Id": 236, 927 | "Name": "Poison Sting", 928 | "Speed": "Fast", 929 | "Type": "Poison", 930 | "Power": 5, 931 | "DurationMs": 600 932 | }, { 933 | "Id": 237, 934 | "Name": "Bubble", 935 | "Speed": "Fast", 936 | "Type": "Water", 937 | "Power": 12, 938 | "DurationMs": 1200 939 | }, { 940 | "Id": 238, 941 | "Name": "Feint Attack", 942 | "Speed": "Fast", 943 | "Type": "Dark", 944 | "Power": 10, 945 | "DurationMs": 900 946 | }, { 947 | "Id": 239, 948 | "Name": "Steel Wing", 949 | "Speed": "Fast", 950 | "Type": "Steel", 951 | "Power": 11, 952 | "DurationMs": 800 953 | }, { 954 | "Id": 240, 955 | "Name": "Fire Fang", 956 | "Speed": "Fast", 957 | "Type": "Fire", 958 | "Power": 11, 959 | "DurationMs": 900 960 | }, { 961 | "Id": 241, 962 | "Name": "Rock Smash", 963 | "Speed": "Fast", 964 | "Type": "Fighting", 965 | "Power": 15, 966 | "DurationMs": 1300 967 | }, { 968 | "Id": 242, 969 | "Name": "Transform", 970 | "Speed": "Fast", 971 | "Type": "Normal", 972 | "Power": 0, 973 | "DurationMs": 2230 974 | }, { 975 | "Id": 243, 976 | "Name": "Counter", 977 | "Speed": "Fast", 978 | "Type": "Fighting", 979 | "Power": 12, 980 | "DurationMs": 900 981 | }, { 982 | "Id": 244, 983 | "Name": "Powder Snow", 984 | "Speed": "Fast", 985 | "Type": "Ice", 986 | "Power": 6, 987 | "DurationMs": 1100 988 | }, { 989 | "Id": 245, 990 | "Name": "Close Combat", 991 | "Speed": "Charge", 992 | "Type": "Fighting", 993 | "Power": 100, 994 | "DurationMs": 2300 995 | }, { 996 | "Id": 246, 997 | "Name": "Dynamic Punch", 998 | "Speed": "Charge", 999 | "Type": "Fighting", 1000 | "Power": 90, 1001 | "DurationMs": 2700 1002 | }, { 1003 | "Id": 247, 1004 | "Name": "Focus Blast", 1005 | "Speed": "Charge", 1006 | "Type": "Fighting", 1007 | "Power": 140, 1008 | "DurationMs": 3500 1009 | }, { 1010 | "Id": 248, 1011 | "Name": "Aurora Beam", 1012 | "Speed": "Charge", 1013 | "Type": "Ice", 1014 | "Power": 80, 1015 | "DurationMs": 3550 1016 | }, { 1017 | "Id": 249, 1018 | "Name": "Charge Beam", 1019 | "Speed": "Fast", 1020 | "Type": "Electric", 1021 | "Power": 8, 1022 | "DurationMs": 1100 1023 | }, { 1024 | "Id": 250, 1025 | "Name": "Volt Switch", 1026 | "Speed": "Fast", 1027 | "Type": "Electric", 1028 | "Power": 20, 1029 | "DurationMs": 2300 1030 | }, { 1031 | "Id": 251, 1032 | "Name": "Wild Charge", 1033 | "Speed": "Charge", 1034 | "Type": "Electric", 1035 | "Power": 90, 1036 | "DurationMs": 2600 1037 | }, { 1038 | "Id": 252, 1039 | "Name": "Zap Cannon", 1040 | "Speed": "Charge", 1041 | "Type": "Electric", 1042 | "Power": 140, 1043 | "DurationMs": 3700 1044 | }, { 1045 | "Id": 253, 1046 | "Name": "Dragon Tail", 1047 | "Speed": "Fast", 1048 | "Type": "Dragon", 1049 | "Power": 15, 1050 | "DurationMs": 1100 1051 | }, { 1052 | "Id": 254, 1053 | "Name": "Avalanche", 1054 | "Speed": "Charge", 1055 | "Type": "Ice", 1056 | "Power": 90, 1057 | "DurationMs": 2700 1058 | }, { 1059 | "Id": 255, 1060 | "Name": "Air Slash", 1061 | "Speed": "Fast", 1062 | "Type": "Flying", 1063 | "Power": 14, 1064 | "DurationMs": 1200 1065 | }, { 1066 | "Id": 256, 1067 | "Name": "Brave Bird", 1068 | "Speed": "Charge", 1069 | "Type": "Flying", 1070 | "Power": 90, 1071 | "DurationMs": 2000 1072 | }, { 1073 | "Id": 257, 1074 | "Name": "Sky Attack", 1075 | "Speed": "Charge", 1076 | "Type": "Flying", 1077 | "Power": 70, 1078 | "DurationMs": 2000 1079 | }, { 1080 | "Id": 258, 1081 | "Name": "Sand Tomb", 1082 | "Speed": "Charge", 1083 | "Type": "Ground", 1084 | "Power": 80, 1085 | "DurationMs": 4000 1086 | }, { 1087 | "Id": 259, 1088 | "Name": "Rock Blast", 1089 | "Speed": "Charge", 1090 | "Type": "Rock", 1091 | "Power": 50, 1092 | "DurationMs": 2100 1093 | }, { 1094 | "Id": 260, 1095 | "Name": "Infestation", 1096 | "Speed": "Fast", 1097 | "Type": "Bug", 1098 | "Power": 10, 1099 | "DurationMs": 1100 1100 | }, { 1101 | "Id": 261, 1102 | "Name": "Struggle Bug", 1103 | "Speed": "Fast", 1104 | "Type": "Bug", 1105 | "Power": 15, 1106 | "DurationMs": 1500 1107 | }, { 1108 | "Id": 262, 1109 | "Name": "Silver Wind", 1110 | "Speed": "Charge", 1111 | "Type": "Bug", 1112 | "Power": 70, 1113 | "DurationMs": 3700 1114 | }, { 1115 | "Id": 263, 1116 | "Name": "Astonish", 1117 | "Speed": "Fast", 1118 | "Type": "Ghost", 1119 | "Power": 8, 1120 | "DurationMs": 1100 1121 | }, { 1122 | "Id": 264, 1123 | "Name": "Hex", 1124 | "Speed": "Fast", 1125 | "Type": "Ghost", 1126 | "Power": 10, 1127 | "DurationMs": 1200 1128 | }, { 1129 | "Id": 265, 1130 | "Name": "Night Shade", 1131 | "Speed": "Charge", 1132 | "Type": "Ghost", 1133 | "Power": 60, 1134 | "DurationMs": 2600 1135 | }, { 1136 | "Id": 266, 1137 | "Name": "Iron Tail", 1138 | "Speed": "Fast", 1139 | "Type": "Steel", 1140 | "Power": 15, 1141 | "DurationMs": 1100 1142 | }, { 1143 | "Id": 267, 1144 | "Name": "Gyro Ball", 1145 | "Speed": "Charge", 1146 | "Type": "Steel", 1147 | "Power": 80, 1148 | "DurationMs": 3300 1149 | }, { 1150 | "Id": 268, 1151 | "Name": "Heavy Slam", 1152 | "Speed": "Charge", 1153 | "Type": "Steel", 1154 | "Power": 70, 1155 | "DurationMs": 2100 1156 | }, { 1157 | "Id": 269, 1158 | "Name": "Fire Spin", 1159 | "Speed": "Fast", 1160 | "Type": "Fire", 1161 | "Power": 14, 1162 | "DurationMs": 1100 1163 | }, { 1164 | "Id": 270, 1165 | "Name": "Overheat", 1166 | "Speed": "Charge", 1167 | "Type": "Fire", 1168 | "Power": 160, 1169 | "DurationMs": 4000 1170 | }, { 1171 | "Id": 271, 1172 | "Name": "Bullet Seed", 1173 | "Speed": "Fast", 1174 | "Type": "Grass", 1175 | "Power": 8, 1176 | "DurationMs": 1100 1177 | }, { 1178 | "Id": 272, 1179 | "Name": "Grass Knot", 1180 | "Speed": "Charge", 1181 | "Type": "Grass", 1182 | "Power": 90, 1183 | "DurationMs": 2600 1184 | }, { 1185 | "Id": 273, 1186 | "Name": "Energy Ball", 1187 | "Speed": "Charge", 1188 | "Type": "Grass", 1189 | "Power": 90, 1190 | "DurationMs": 3900 1191 | }, { 1192 | "Id": 274, 1193 | "Name": "Extrasensory", 1194 | "Speed": "Fast", 1195 | "Type": "Psychic", 1196 | "Power": 12, 1197 | "DurationMs": 1100 1198 | }, { 1199 | "Id": 275, 1200 | "Name": "Futuresight", 1201 | "Speed": "Charge", 1202 | "Type": "Psychic", 1203 | "Power": 120, 1204 | "DurationMs": 2700 1205 | }, { 1206 | "Id": 276, 1207 | "Name": "Mirror Coat", 1208 | "Speed": "Charge", 1209 | "Type": "Psychic", 1210 | "Power": 60, 1211 | "DurationMs": 2600 1212 | }, { 1213 | "Id": 277, 1214 | "Name": "Outrage", 1215 | "Speed": "Charge", 1216 | "Type": "Dragon", 1217 | "Power": 110, 1218 | "DurationMs": 3900 1219 | }, { 1220 | "Id": 278, 1221 | "Name": "Snarl", 1222 | "Speed": "Fast", 1223 | "Type": "Dark", 1224 | "Power": 12, 1225 | "DurationMs": 1100 1226 | }, { 1227 | "Id": 279, 1228 | "Name": "Crunch", 1229 | "Speed": "Charge", 1230 | "Type": "Dark", 1231 | "Power": 70, 1232 | "DurationMs": 3200 1233 | }, { 1234 | "Id": 280, 1235 | "Name": "Foul Play", 1236 | "Speed": "Charge", 1237 | "Type": "Dark", 1238 | "Power": 70, 1239 | "DurationMs": 2000 1240 | }, { 1241 | "Id": 281, 1242 | "Name": "Hidden Power", 1243 | "Speed": "Fast", 1244 | "Type": "Normal", 1245 | "Power": 15, 1246 | "DurationMs": 1500 1247 | }] -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pokemon Retriever 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | Loading... 29 | 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ivreader", 3 | "version": "0.0.1", 4 | "description": "Requests information about Pokemon currently in inventory from Niantic and displays stats for each Pokemon in a table.", 5 | "author": "eric121492@gmail.com", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:Eric-Carlton/PokemonGoReader.git" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "@angular/common": "2.0.0-rc.4", 13 | "@angular/compiler": "2.0.0-rc.4", 14 | "@angular/core": "2.0.0-rc.4", 15 | "@angular/forms": "0.2.0", 16 | "@angular/http": "2.0.0-rc.4", 17 | "@angular/platform-browser": "2.0.0-rc.4", 18 | "@angular/platform-browser-dynamic": "2.0.0-rc.4", 19 | "@angular/router": "3.0.0-beta.1", 20 | "@angular/router-deprecated": "2.0.0-rc.2", 21 | "@angular/upgrade": "2.0.0-rc.4", 22 | "angular2-in-memory-web-api": "0.0.14", 23 | "body-parser": "^1.15.2", 24 | "bootstrap": "^3.3.7", 25 | "bunyan": "^1.8.1", 26 | "core-js": "^2.4.0", 27 | "express": "^4.14.0", 28 | "http-server": "^0.9.0", 29 | "jquery": "^3.1.0", 30 | "pogobuf": "^1.10.0", 31 | "reflect-metadata": "^0.1.3", 32 | "rimraf": "^2.5.4", 33 | "rxjs": "5.0.0-beta.6", 34 | "systemjs": "0.19.27", 35 | "uglify-js": "^2.7.3", 36 | "zone.js": "^0.6.12" 37 | }, 38 | "scripts": { 39 | "start": "concurrently \"npm run server\" \"npm run webapp\"", 40 | "start-dev": "concurrently \"npm run server\" \"npm run webapp-dev\"", 41 | "server": "node server/index.js | node node_modules/bunyan/bin/bunyan", 42 | "webapp": "npm run clean && tsc && npm run uglify && node ./node_modules/http-server/bin/http-server", 43 | "webapp-dev": "npm run clean && tsc && npm run uglify-dev && concurrently \"npm run tsc:w\" \"npm run lite\"", 44 | "lite": "lite-server", 45 | "postinstall": "node ./node_modules/typings install", 46 | "tsc": "tsc", 47 | "tsc:w": "tsc -w", 48 | "typings": "node ./node_modules/typings", 49 | "clean": "node ./node_modules/rimraf/bin.js ./webapp/js/*", 50 | "uglify": "node ./node_modules/uglify-js/bin/uglifyjs webapp/js/app.js -o webapp/js/app.min.js -m -c", 51 | "uglify-dev": "node ./node_modules/uglify-js/bin/uglifyjs webapp/js/app.js -o webapp/js/app.min.js --in-source-map webapp/js/app.js.map --source-map webapp/js/app.min.js.map --source-map-url app.min.js.map -m -c" 52 | }, 53 | "jshintConfig": { 54 | "esversion": 6, 55 | "node": true 56 | }, 57 | "devDependencies": { 58 | "jshint": "^2.9.2", 59 | "concurrently": "^2.0.0", 60 | "lite-server": "^2.2.0", 61 | "typescript": "^1.8.10", 62 | "typings": "^1.0.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/config/properties.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | log: { 3 | levels: { 4 | console:'trace' 5 | }, 6 | names: { 7 | index:'server.index', 8 | pokemonRoutes:'server.pokemonRoutes', 9 | expressUtils:'server.expressUtils', 10 | pokemonUtils:'server.pokemonUtils' 11 | } 12 | }, 13 | server: { 14 | port: 8008, 15 | listeningMsg:'Listening on port %s...' 16 | }, 17 | cors: { 18 | allowOrigin:'*', 19 | allowHeaders:'Origin, X-Requested-With, Content-Type, Accept' 20 | }, 21 | routes: { 22 | root:'/api', 23 | pokemon:'/pokemon/get', 24 | transfer:'/pokemon/transfer', 25 | rename:'/pokemon/rename', 26 | favorite:'/pokemon/favorite' 27 | }, 28 | errors: { 29 | token:'token is required', 30 | username:'username is required', 31 | password:'password is required', 32 | type:'type is required', 33 | pokemon_id:'pokemon_id is required', 34 | nickname:'nickname is required', 35 | invalid_pokemon_id:'pokemon_id is not valid', 36 | inventory:'Error occurred getting inventory', 37 | login:'Error occurred logging in', 38 | transfer:'Error occurred transferring pokemon', 39 | req_favorite:'favorite is required', 40 | favorite:'Error occurred setting favorite' 41 | }, 42 | pokemonCpMulipliersByLevel: { 43 | "1": 0.094, 44 | "1.5": 0.135137432, 45 | "2": 0.16639787, 46 | "2.5": 0.192650919, 47 | "3": 0.21573247, 48 | "3.5": 0.236572661, 49 | "4": 0.25572005, 50 | "4.5": 0.273530381, 51 | "5": 0.29024988, 52 | "5.5": 0.306057377, 53 | "6": 0.3210876, 54 | "6.5": 0.335445036, 55 | "7": 0.34921268, 56 | "7.5": 0.362457751, 57 | "8": 0.37523559, 58 | "8.5": 0.387592406, 59 | "9": 0.39956728, 60 | "9.5": 0.411193551, 61 | "10": 0.42250001, 62 | "10.5": 0.432926419, 63 | "11": 0.44310755, 64 | "11.5": 0.4530599578, 65 | "12": 0.46279839, 66 | "12.5": 0.472336083, 67 | "13": 0.48168495, 68 | "13.5": 0.4908558, 69 | "14": 0.49985844, 70 | "14.5": 0.508701765, 71 | "15": 0.51739395, 72 | "15.5": 0.525942511, 73 | "16": 0.53435433, 74 | "16.5": 0.542635767, 75 | "17": 0.55079269, 76 | "17.5": 0.558830576, 77 | "18": 0.56675452, 78 | "18.5": 0.574569153, 79 | "19": 0.58227891, 80 | "19.5": 0.589887917, 81 | "20": 0.59740001, 82 | "20.5": 0.604818814, 83 | "21": 0.61215729, 84 | "21.5": 0.619399365, 85 | "22": 0.62656713, 86 | "22.5": 0.633644533, 87 | "23": 0.64065295, 88 | "23.5": 0.647576426, 89 | "24": 0.65443563, 90 | "24.5": 0.661214806, 91 | "25": 0.667934, 92 | "25.5": 0.674577537, 93 | "26": 0.68116492, 94 | "26.5": 0.687680648, 95 | "27": 0.69414365, 96 | "27.5": 0.700538673, 97 | "28": 0.70688421, 98 | "28.5": 0.713164996, 99 | "29": 0.71939909, 100 | "29.5": 0.725571552, 101 | "30": 0.7317, 102 | "30.5": 0.734741009, 103 | "31": 0.73776948, 104 | "31.5": 0.740785574, 105 | "32": 0.74378943, 106 | "32.5": 0.746781211, 107 | "33": 0.74976104, 108 | "33.5": 0.752729087, 109 | "34": 0.75568551, 110 | "34.5": 0.758630378, 111 | "35": 0.76156384, 112 | "35.5": 0.764486065, 113 | "36": 0.76739717, 114 | "36.5": 0.770297266, 115 | "37": 0.7731865, 116 | "37.5": 0.776064962, 117 | "38": 0.77893275, 118 | "38.5": 0.781790055, 119 | "39": 0.78463697, 120 | "39.5": 0.787473578, 121 | "40": 0.79030001 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bunyan = require('bunyan'); 4 | const express = require('express'); 5 | 6 | const props = require('./config/properties.js'); 7 | const expressUtils = require('./utils/expressUtils.js'); 8 | const pokemonRoutes = require('./routes/pokemonRoutes.js'); 9 | 10 | const log = bunyan.createLogger({ 11 | name: props.log.names.index, 12 | streams: [{ 13 | level: props.log.levels.console, 14 | stream: process.stdout 15 | }] 16 | }); 17 | 18 | const app = express(); 19 | 20 | expressUtils.initApp(app); 21 | 22 | pokemonRoutes.createRoutes(app); 23 | 24 | const server = app.listen(props.server.port, () => { 25 | log.info(props.server.listeningMsg, props.server.port); 26 | }); -------------------------------------------------------------------------------- /server/models/Credential.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Credential { 4 | constructor( 5 | login_type, 6 | token, 7 | username, 8 | password 9 | ) 10 | { 11 | this.login_type = login_type.toLowerCase(); 12 | this.token = token; 13 | this.username = username; 14 | this.password = password; 15 | } 16 | } 17 | 18 | module.exports = Credential; 19 | -------------------------------------------------------------------------------- /server/models/Pokemon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Pokemon { 4 | constructor( 5 | pokedex_number, 6 | name, 7 | species, 8 | attack_iv, 9 | defense_iv, 10 | stamina_iv, 11 | current_hp, 12 | max_hp, 13 | iv_percentage, 14 | cp, 15 | candy, 16 | favorite, 17 | family_name, 18 | id, 19 | move_1, 20 | move_2, 21 | caught_time, 22 | level 23 | ) 24 | { 25 | this.pokedex_number = pokedex_number; 26 | this.name = name; 27 | this.species = species; 28 | this.attack_iv = attack_iv; 29 | this.defense_iv = defense_iv; 30 | this.stamina_iv = stamina_iv; 31 | this.current_hp = current_hp; 32 | this.max_hp = max_hp; 33 | this.iv_percentage = iv_percentage; 34 | this.cp = cp; 35 | this.candy = candy; 36 | this.favorite = favorite; 37 | this.family_name = family_name; 38 | this.id = id; 39 | this.move_1 = move_1; 40 | this.move_2 = move_2; 41 | this.caught_time = caught_time; 42 | this.level = level; 43 | } 44 | } 45 | 46 | module.exports = Pokemon; 47 | -------------------------------------------------------------------------------- /server/models/Species.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Species { 4 | constructor( 5 | pokedex_number, 6 | species, 7 | count, 8 | candy, 9 | evolve_sort, 10 | evolve, 11 | transfer, 12 | need 13 | ) 14 | { 15 | this.pokedex_number = pokedex_number; 16 | this.species = species; 17 | this.count = count; 18 | this.candy = candy; 19 | this.evolve_sort = evolve_sort; 20 | this.evolve = evolve; 21 | this.transfer = transfer; 22 | this.need = need; 23 | } 24 | } 25 | 26 | module.exports = Species; 27 | -------------------------------------------------------------------------------- /server/routes/pokemonRoutes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bunyan = require('bunyan'); 4 | const pogobuf = require('pogobuf'); 5 | 6 | const props = require('../config/properties.js'); 7 | const expressUtils = require('../utils/expressUtils.js'); 8 | const pokemonUtils = require('../utils/pokemonUtils.js'); 9 | const pokemonData = require('../../data/pokemon.json'); 10 | 11 | const Credential = require('../models/Credential.js'); 12 | const Pokemon = require('../models/Pokemon.js'); 13 | const Species = require('../models/Species.js'); 14 | 15 | const log = bunyan.createLogger({ 16 | name: props.log.names.pokemonRoutes, 17 | streams: [{ 18 | level: props.log.levels.console, 19 | stream: process.stdout 20 | }] 21 | }); 22 | 23 | module.exports = { 24 | createRoutes: (app) => { 25 | app.post(props.routes.root + props.routes.pokemon, (req, res, next) => { 26 | const endpoint = props.routes.root + props.routes.pokemon; 27 | log.debug( 28 | {username: req.body.username}, 29 | 'POST request to ' + endpoint); 30 | 31 | if(!req.body.hasOwnProperty('username')){ 32 | expressUtils.sendResponse(res, next, 400, {error: props.errors.username}, req.body.username, endpoint); 33 | } else if (!req.body.hasOwnProperty('password')) { 34 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 35 | } else if (!req.body.hasOwnProperty('type')) { 36 | expressUtils.sendResponse(res, next, 400, {error: props.errors.type}, req.body.username, endpoint); 37 | } else { 38 | let credential = new Credential(req.body.type, /* token */ null, req.body.username, req.body.password); 39 | pokemonUtils.getClient(credential).then(client => { 40 | client.getInventory(0).then(inventory => { 41 | if (!inventory.success){ 42 | expressUtils.sendResponse(res, next, 500, {error: props.errors.inventory}, req.body.username, endpoint); 43 | } 44 | 45 | let splitInventory = pogobuf.Utils.splitInventory(inventory); 46 | 47 | let rawPokemon = splitInventory.pokemon; 48 | 49 | let formattedPokemon = []; 50 | 51 | let speciesMap = {}; 52 | 53 | for(let i = 0; i < rawPokemon.length; i++){ 54 | let pokemon = rawPokemon[i]; 55 | 56 | if(Object.keys(pokemon).length > 0 && pokemon.hasOwnProperty('is_egg') && !pokemon.is_egg){ 57 | let id = pokemon.pokemon_id.toString(); 58 | 59 | let species = { 60 | 'pokedex_number': pokemon.pokemon_id, 61 | 'species': pokemonData[id].Name, 62 | 'count': 1, 63 | 'candy': 0, 64 | 'evolve_sort': 0, 65 | 'evolve': [], 66 | 'transfer': 0, 67 | 'need': 0 68 | }; 69 | 70 | if (id in speciesMap){ 71 | speciesMap[id].count += 1; 72 | } else { 73 | species.candy = pokemonUtils.getCandy(pokemon, splitInventory.candies); 74 | speciesMap[id] = species; 75 | } 76 | 77 | formattedPokemon.push(new Pokemon( 78 | pokemon.pokemon_id, 79 | pokemonUtils.getName(pokemon), 80 | pokemonData[pokemon.pokemon_id].Name, 81 | pokemon.individual_attack, 82 | pokemon.individual_defense, 83 | pokemon.individual_stamina, 84 | pokemon.stamina, 85 | pokemon.stamina_max, 86 | parseFloat(((pokemon.individual_attack + pokemon.individual_defense + pokemon.individual_stamina) / 45 * 100).toFixed(2)), 87 | pokemon.cp, 88 | pokemonUtils.getCandy(pokemon, splitInventory.candies), 89 | pokemon.favorite === 1, 90 | pokemonData[pokemonData[pokemon.pokemon_id].FamilyId].Name, 91 | pokemon.id, 92 | pokemon.move_1, 93 | pokemon.move_2, 94 | pokemon.creation_time_ms, 95 | pokemonUtils.getLevel(pokemon) 96 | )); 97 | } 98 | } 99 | 100 | let formattedSpecies = []; 101 | Object.keys(speciesMap).forEach(function(speciesId) { 102 | let species = speciesMap[speciesId]; 103 | let family = []; 104 | 105 | if(pokemonData[speciesId].CandyToEvolve > 0) { 106 | let requiredCandy1 = pokemonData[speciesId].CandyToEvolve; 107 | pokemonData[speciesId].EvolutionIds.forEach(function(evolution1Id){ 108 | family.push({ 109 | id: evolution1Id.toString(), 110 | cost: requiredCandy1 111 | }); 112 | 113 | let requiredCandy2 = requiredCandy1 + pokemonData[evolution1Id].CandyToEvolve; 114 | pokemonData[evolution1Id].EvolutionIds.forEach(function(evolution2Id){ 115 | family.push({ 116 | id: evolution2Id.toString(), 117 | cost: requiredCandy2 118 | }); 119 | }); 120 | }); 121 | } 122 | 123 | let maxDesc = 0; 124 | family.forEach(function(descendant){ 125 | let canEvolve = Math.trunc((species.candy - 1) / (descendant.cost - 1)); 126 | if (canEvolve > 0){ 127 | species.evolve_sort = Math.max(species.evolve_sort, canEvolve); 128 | species.evolve.push({'id': descendant.id, 'canEvolve': canEvolve}); 129 | } else { 130 | if (maxDesc < descendant.cost){ 131 | maxDesc = descendant.cost; 132 | species.need = Math.ceil((descendant.cost - species.candy) / 4); 133 | } 134 | } 135 | }); 136 | 137 | if (species.evolve_sort > 0 && species.count > species.evolve_sort) { 138 | species.transfer = species.count - species.evolve_sort; 139 | } 140 | 141 | formattedSpecies.push(new Species( 142 | species.pokedex_number, 143 | species.species, 144 | species.count, 145 | species.candy, 146 | species.evolve_sort, 147 | species.evolve, 148 | species.transfer, 149 | species.need 150 | )); 151 | }); 152 | 153 | expressUtils.sendResponse(res, next, 200, {pokemon: formattedPokemon, species: formattedSpecies, token: client.options.authToken}, req.body.username, endpoint); 154 | }, err => { 155 | log.error({err: err.message}); 156 | expressUtils.sendResponse(res, next, 500, {error: props.errors.inventory}, req.body.username, endpoint) 157 | }); 158 | }, err => { 159 | log.error({err: err.message}, 'pokemonUtils.getClient() failed'); 160 | expressUtils.sendResponse(res, next, 500, {error: props.errors.inventory}, req.body.username, endpoint); 161 | }); 162 | } 163 | }); 164 | 165 | app.post(props.routes.root + props.routes.transfer, (req, res, next) => { 166 | const endpoint = props.routes.root + props.routes.transfer; 167 | log.debug( 168 | {username: req.body.username}, 169 | 'POST request to ' + endpoint); 170 | 171 | if(!req.body.hasOwnProperty('token')){ 172 | expressUtils.sendResponse(res, next, 400, {error: props.errors.token}, req.body.username, endpoint); 173 | } else if (!req.body.hasOwnProperty('username')) { 174 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 175 | } else if (!req.body.hasOwnProperty('password')) { 176 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 177 | } else if (!req.body.hasOwnProperty('type')) { 178 | expressUtils.sendResponse(res, next, 400, {error: props.errors.type}, req.body.username, endpoint); 179 | } else if (!req.body.hasOwnProperty('id')) { 180 | expressUtils.sendResponse(res, next, 400, {error: props.errors.pokemon_id}, req.body.username, endpoint); 181 | } 182 | else { 183 | let credential = new Credential(req.body.type, req.body.token, req.body.username, req.body.password); 184 | pokemonUtils.getClient(credential).then(client => { 185 | client.releasePokemon(req.body.id).then(releaseResponse => { 186 | expressUtils.sendResponse(res, next, 200, {success: releaseResponse.result === 1, token: client.authToken}, req.body.username, endpoint); 187 | }, err => { 188 | log.error({err: err.message}, 'client.releasePokemon() failed'); 189 | expressUtils.sendResponse(res, next, 500, {error: props.errors.transfer}, req.body.username, endpoint); 190 | }); 191 | }, err => { 192 | log.error({err: err.message}, 'pokemonUtils.getClient() failed'); 193 | expressUtils.sendResponse(res, next, 500, {error: props.errors.login}, req.body.username, endpoint); 194 | }); 195 | } 196 | }); 197 | 198 | app.post(props.routes.root + props.routes.rename, (req, res, next) => { 199 | const endpoint = props.routes.root + props.routes.rename; 200 | log.debug( 201 | {username: req.body.username}, 202 | 'POST request to ' + endpoint); 203 | 204 | if(!req.body.hasOwnProperty('token')){ 205 | expressUtils.sendResponse(res, next, 400, {error: props.errors.token}, req.body.username, endpoint); 206 | } else if (!req.body.hasOwnProperty('username')) { 207 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 208 | } else if (!req.body.hasOwnProperty('password')) { 209 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 210 | } else if (!req.body.hasOwnProperty('type')) { 211 | expressUtils.sendResponse(res, next, 400, {error: props.errors.type}, req.body.username, endpoint); 212 | } else if (!req.body.hasOwnProperty('id')) { 213 | expressUtils.sendResponse(res, next, 400, {error: props.errors.pokemon_id}, req.body.username, endpoint); 214 | } 215 | else { 216 | let credential = new Credential(req.body.type, req.body.token, req.body.username, req.body.password); 217 | pokemonUtils.getClient(credential).then(client => { 218 | client.nicknamePokemon(req.body.id, req.body.nickname).then(nicknameResponse => { 219 | expressUtils.sendResponse(res, next, 200, {success: nicknameResponse.result === 1, token: client.authToken}, req.body.username, endpoint); 220 | }, err => { 221 | log.error({err: err.message}, 'client.nicknamePokemon() failed'); 222 | expressUtils.sendResponse(res, next, 500, {error: props.errors.transfer}, req.body.username, endpoint); 223 | }); 224 | }, err => { 225 | log.error({err: err.message}, 'pokemonUtils.getClient() failed'); 226 | expressUtils.sendResponse(res, next, 500, {error: props.errors.login}, req.body.username, endpoint); 227 | }); 228 | } 229 | }); 230 | 231 | app.post(props.routes.root + props.routes.favorite, (req, res,next) => { 232 | const endpoint = props.routes.root + props.routes.favorite; 233 | log.debug( 234 | {username: req.body.username}, 235 | 'POST request to ' + endpoint); 236 | 237 | if(!req.body.hasOwnProperty('token')){ 238 | expressUtils.sendResponse(res, next, 400, {error: props.errors.token}, req.body.username, endpoint); 239 | } else if (!req.body.hasOwnProperty('username')) { 240 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 241 | } else if (!req.body.hasOwnProperty('password')) { 242 | expressUtils.sendResponse(res, next, 400, {error: props.errors.password}, req.body.username, endpoint); 243 | } else if (!req.body.hasOwnProperty('type')) { 244 | expressUtils.sendResponse(res, next, 400, {error: props.errors.type}, req.body.username, endpoint); 245 | } else if (!req.body.hasOwnProperty('id')) { 246 | expressUtils.sendResponse(res, next, 400, {error: props.errors.pokemon_id}, req.body.username, endpoint); 247 | } else if (!req.body.hasOwnProperty('favorite')){ 248 | expressUtils.sendResponse(res, next, 400, {error: props.errors.req_favorite}, req.body.username, endpoint); 249 | } 250 | else { 251 | let credential = new Credential(req.body.type, req.body.token, req.body.username, req.body.password); 252 | pokemonUtils.getClient(credential).then(client => { 253 | client.setFavoritePokemon(req.body.id, req.body.favorite).then(favoriteResponse => { 254 | expressUtils.sendResponse(res, next, 200, {success: favoriteResponse.result === 1, token: client.authToken}, req.body.username, endpoint); 255 | }, err => { 256 | log.error({err: err.message}, 'client.setFavoritePokemon() failed'); 257 | expressUtils.sendResponse(res, next, 500, {error: props.errors.favorite}, req.body.username, endpoint); 258 | }); 259 | }, err => { 260 | log.error({err: err.message}, 'pokemonUtils.getClient() failed'); 261 | expressUtils.sendResponse(res, next, 500, {error: props.errors.login}, req.body.username, endpoint); 262 | }); 263 | } 264 | }); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /server/utils/expressUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bunyan = require('bunyan'); 4 | const bodyParser = require('body-parser'); 5 | 6 | const props = require('../config/properties.js'); 7 | 8 | const log = bunyan.createLogger({ 9 | name: props.log.names.expressUtils, 10 | streams: [{ 11 | level: props.log.levels.console, 12 | stream: process.stdout 13 | }] 14 | }) 15 | 16 | module.exports = { 17 | sendResponse: (res, next, status, response, username, endpoint) => { 18 | log.debug({status: status, username: username}, 'Sending response from ' + endpoint); 19 | res.status(status).send(response); 20 | next(); 21 | }, 22 | initApp: (app) => { 23 | app.use(bodyParser.json()); 24 | 25 | app.use((req, res, next) => { 26 | res.header('Access-Control-Allow-Origin', props.cors.allowOrigin); 27 | res.header('Access-Control-Allow-Headers', props.cors.allowHeaders); 28 | next(); 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /server/utils/pokemonUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const bunyan = require('bunyan'); 4 | const pogobuf = require('pogobuf'); 5 | const props = require('../config/properties.js'); 6 | const log = bunyan.createLogger({ 7 | name: props.log.names.pokemonUtils, 8 | streams: [{ 9 | level: props.log.levels.console, 10 | stream: process.stdout 11 | }] 12 | }); 13 | const pokemonData = require('../../data/pokemon.json'); 14 | let privateProps = null; 15 | try{ 16 | privateProps = require('../config/private.json'); 17 | } catch(err) { 18 | log.error({err: err.message}); 19 | throw new Error('A file named private.json must be created in server/config that contains a hashing key'); 20 | } 21 | 22 | let getToken = (credential) => { 23 | return new Promise((resolve, reject) => { 24 | let login = null; 25 | 26 | if (credential.login_type === 'google') { 27 | login = new pogobuf.GoogleLogin(); 28 | } else { 29 | login = new pogobuf.PTCLogin(); 30 | } 31 | 32 | login.login(credential.username, credential.password).then(token => { 33 | resolve(token); 34 | }, err => { 35 | log.trace('login.login() failed, rejecting from getToken'); 36 | reject(err); 37 | }); 38 | }); 39 | } 40 | 41 | module.exports = { 42 | getClient: (credential) => { 43 | return new Promise((resolve, reject) => { 44 | let client = null; 45 | 46 | if (privateProps.hashingKey) { 47 | client = new pogobuf.Client({ 48 | useHashingServer: true, 49 | version: 6100, 50 | hashingKey: privateProps.hashingKey 51 | }); 52 | } else { 53 | throw new Error('server/private.json must contain a JSON object with a single property named "hashingKey" whose value is a valid hashing key'); 54 | } 55 | 56 | 57 | if (credential.token) { 58 | client.setAuthInfo(credential.login_type, credential.token); 59 | client.init().then(() => { 60 | resolve(client); 61 | }, err => { 62 | getToken(credential).then(token => { 63 | client.setAuthInfo(credential.login_type, token); 64 | client.init().then(() => { 65 | resolve(client); 66 | }, err => { 67 | log.trace('client.init() failed, rejecting from getClient'); 68 | reject(err); 69 | }); 70 | }, err => { 71 | log.trace('getToken() failed, rejecting from getClient'); 72 | reject(err); 73 | }); 74 | }); 75 | } else { 76 | getToken(credential).then(token => { 77 | client.setAuthInfo(credential.login_type, token); 78 | client.init().then(() => { 79 | resolve(client); 80 | }, err => { 81 | log.trace('client.init() failed, rejecting from getClient'); 82 | reject(err); 83 | }); 84 | }, err => { 85 | log.trace('getToken() failed, rejecting from getClient'); 86 | reject(err); 87 | }); 88 | } 89 | }); 90 | }, 91 | 92 | getLevel: (pokemon) => { 93 | let cp = pokemon.cp_multiplier; 94 | if (pokemon.hasOwnProperty('additional_cp_multiplier')) { 95 | cp += pokemon.additional_cp_multiplier 96 | } 97 | 98 | for (let level in props.pokemonCpMulipliersByLevel) { 99 | if (props.pokemonCpMulipliersByLevel.hasOwnProperty(level) && 100 | Math.abs(cp - props.pokemonCpMulipliersByLevel[level]) < 0.0001) { 101 | return parseFloat(level); 102 | } 103 | } 104 | 105 | return 0; 106 | }, 107 | 108 | getName: (pokemon) => { 109 | if (pokemon.hasOwnProperty('nickname') && pokemon.nickname.length > 0) { 110 | return pokemon.nickname; 111 | } 112 | 113 | return pokemonData[pokemon.pokemon_id].Name; 114 | }, 115 | 116 | getCandy: (pokemon, candies) => { 117 | for (let j = 0; j < candies.length; j++) { 118 | let candy = candies[j]; 119 | 120 | if (candy.hasOwnProperty('family_id') && candy.hasOwnProperty('family_id') && candy.family_id === pokemonData[pokemon.pokemon_id].FamilyId) { 121 | return candy.candy; 122 | } 123 | } 124 | 125 | return 0; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /systemjs.config.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | // map tells the System loader where to look for things 3 | var map = { 4 | 'app': 'app', // 'dist', 5 | '@angular': 'node_modules/@angular', 6 | 'rxjs': 'node_modules/rxjs' 7 | }; 8 | // packages tells the System loader how to load when no filename and/or no extension 9 | var packages = { 10 | 'rxjs': { 11 | defaultExtension: 'js' 12 | }, 13 | 'angular2-in-memory-web-api': { 14 | main: 'index.js', 15 | defaultExtension: 'js' 16 | }, 17 | }; 18 | var ngPackageNames = [ 19 | 'common', 20 | 'compiler', 21 | 'core', 22 | 'forms', 23 | 'http', 24 | 'platform-browser', 25 | 'platform-browser-dynamic', 26 | 'router', 27 | 'router-deprecated', 28 | 'upgrade', 29 | ]; 30 | // Individual files (~300 requests): 31 | function packIndex(pkgName) { 32 | packages['@angular/' + pkgName] = { 33 | main: 'index.js', 34 | defaultExtension: 'js' 35 | }; 36 | } 37 | // Bundled (~40 requests): 38 | function packUmd(pkgName) { 39 | packages['@angular/' + pkgName] = { 40 | main: '/bundles/' + pkgName + '.umd.js', 41 | defaultExtension: 'js' 42 | }; 43 | } 44 | // Most environments should use UMD; some (Karma) need the individual index files 45 | var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; 46 | // Add package entries for angular packages 47 | ngPackageNames.forEach(setPackageConfig); 48 | var config = { 49 | map: map, 50 | packages: packages 51 | }; 52 | System.config(config); 53 | })(this); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "system", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": true, 10 | "noImplicitAny": false, 11 | "outFile": "webapp/js/app.js" 12 | }, 13 | "include": [ 14 | "webapp/*.ts", 15 | "webapp/**/*.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "core-js": "registry:dt/core-js#0.0.0+20160602141332", 4 | "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", 5 | "node": "registry:dt/node#6.0.0+20160621231320" 6 | } 7 | } -------------------------------------------------------------------------------- /webapp/components/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { LoginComponent } from './login.component' 4 | import { PokemonStatsComponent } from './pokemon-stats.component' 5 | 6 | declare let gapi: any; 7 | 8 | @Component({ 9 | selector: 'app', 10 | templateUrl: './webapp/templates/app.component.html', 11 | directives: [LoginComponent, PokemonStatsComponent] 12 | }) 13 | 14 | export class AppComponent { 15 | } -------------------------------------------------------------------------------- /webapp/components/login-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { UserLogin } from '../models/user-login.model' 5 | 6 | import { PokemonService } from '../services/pokemon.service' 7 | import { PropertiesService } from '../services/properties.service'; 8 | import { UtilsService } from '../services/utils.service' 9 | import { SortService } from '../services/sort.service' 10 | 11 | @Component({ 12 | selector: 'login-form', 13 | templateUrl: './webapp/templates/login-form.component.html', 14 | styleUrls: ['./webapp/styles/login-form.component.css'] 15 | }) 16 | 17 | export class LoginFormComponent { 18 | private _model: UserLogin = new UserLogin("", "", /* token */ null, this._properties.loginTypes[0]); 19 | private _loading: boolean = false; 20 | private _submitted: boolean = false; 21 | private _loginErrorMessage = null; 22 | 23 | constructor( 24 | private _pokemonService: PokemonService, 25 | private _properties: PropertiesService, 26 | private _utils: UtilsService, 27 | private _sortService: SortService 28 | ){} 29 | 30 | public get submitted(): boolean{ 31 | return this._submitted; 32 | } 33 | 34 | private _changedLoginType(value: string){ 35 | let loginTypes = this._properties.loginTypes.slice(); 36 | loginTypes.splice(this._properties.loginTypes.indexOf(value), 1); 37 | loginTypes.unshift(value); 38 | this._utils.setLocalStorageObj('loginTypes', loginTypes); 39 | } 40 | 41 | private _onSubmit() { 42 | this._loading = true; 43 | this._loginErrorMessage = null; 44 | 45 | this._pokemonService.userLogin = this._model; 46 | 47 | this._pokemonService.retrievePokemon().then( () => { 48 | this._sortService.sortPokemon(this._properties.defaultPokemonTableSortOrder, false); 49 | this._sortService.sortSpecies(this._properties.defaultSpeciesSortOrder, false); 50 | this._loading = false; 51 | this._submitted = true; 52 | }, err => { 53 | this._loading = false; 54 | this._loginErrorMessage = this._properties.loginErrorMessage; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /webapp/components/login.component.ts: -------------------------------------------------------------------------------- 1 | import { ViewChild, Component } from '@angular/core'; 2 | 3 | import { PropertiesService } from '../services/properties.service' 4 | 5 | import { LoginFormComponent } from './login-form.component' 6 | 7 | declare let gapi: any; 8 | 9 | @Component({ 10 | selector: 'login', 11 | templateUrl: './webapp/templates/login.component.html' , 12 | styleUrls: ['./webapp/styles/login.component.css'], 13 | directives: [LoginFormComponent] 14 | }) 15 | 16 | export class LoginComponent { 17 | private _title : string = this._properties.loginComponentTitle; 18 | private _content : string = this._properties.loginComponentContent; 19 | 20 | @ViewChild(LoginFormComponent) loginForm: LoginFormComponent; 21 | 22 | constructor(private _properties: PropertiesService) { } 23 | } -------------------------------------------------------------------------------- /webapp/components/pokemon-species.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PropertiesService } from '../services/properties.service' 4 | import { PokemonService } from '../services/pokemon.service' 5 | import { SortService } from '../services/sort.service' 6 | 7 | import { SortType } from '../models/sort-type.model' 8 | import { Species } from '../models/species.model' 9 | 10 | import { SettingsComponent } from './settings.component' 11 | 12 | import { PrependZerosPipe } from '../pipes/prepend-zeros.pipe' 13 | 14 | @Component({ 15 | selector: 'pokemon-species', 16 | templateUrl: './webapp/templates/pokemon-species.component.html', 17 | styleUrls: ['./webapp/styles/pokemon-species.component.css'], 18 | directives: [SettingsComponent], 19 | pipes: [PrependZerosPipe] 20 | }) 21 | 22 | export class PokemonSpeciesComponent { 23 | constructor( 24 | private _properties: PropertiesService, 25 | private _pokemonService: PokemonService, 26 | private _sortService: SortService 27 | ) {} 28 | } 29 | -------------------------------------------------------------------------------- /webapp/components/pokemon-stats.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PropertiesService } from '../services/properties.service' 4 | import { PokemonService } from '../services/pokemon.service' 5 | import { SortService } from '../services/sort.service' 6 | import { ExportService } from '../services/export.service' 7 | import { SettingsService } from '../services/settings.service' 8 | 9 | import { PokemonTableComponent } from './pokemon-table.component' 10 | import { PokemonSpeciesComponent } from './pokemon-species.component' 11 | 12 | @Component({ 13 | selector: 'pokemon-stats', 14 | templateUrl: './webapp/templates/pokemon-stats.component.html', 15 | styleUrls: ['./webapp/styles/pokemon-stats.component.css'], 16 | directives: [PokemonTableComponent, PokemonSpeciesComponent] 17 | }) 18 | 19 | export class PokemonStatsComponent { 20 | private _title: string = this._properties.pokemonStatsComponentTitle; 21 | private _content: string = this._properties.pokemonStatsComponentContent; 22 | private _refreshingPokemon: boolean = false; 23 | 24 | constructor( 25 | private _properties: PropertiesService, 26 | private _pokemonService: PokemonService, 27 | private _exportService: ExportService, 28 | private _sortService: SortService, 29 | private _settingsService: SettingsService 30 | ){} 31 | 32 | private _export() { 33 | let link = document.getElementById('csvDownloadLink'); 34 | let now = new Date(); 35 | link.setAttribute('download', 'pokemon.' + now.getTime() + '.csv'); 36 | link.href = 'data:text/plain;charset=utf-8,' + this._exportService.exportPokemon( 37 | this._settingsService.pokemonTableStats 38 | ); 39 | } 40 | 41 | private _refreshPokemon(){ 42 | if(!this._refreshingPokemon){ 43 | this._refreshingPokemon = true; 44 | this._pokemonService.retrievePokemon().then( () => { 45 | if(this._sortService.pokemonSortOrder === ''){ 46 | this._sortService.sortPokemon(this._properties.defaultPokemonTableSortOrder, false); 47 | 48 | } else { 49 | this._sortService.sortPokemon(this._sortService.pokemonSortOrder, false); 50 | } 51 | 52 | if(this._sortService.speciesSortOrder === ''){ 53 | this._sortService.sortSpecies(this._properties.defaultSpeciesSortOrder, false); 54 | } else { 55 | this._sortService.sortSpecies(this._sortService.speciesSortOrder, false); 56 | } 57 | 58 | this._refreshingPokemon = false; 59 | }, err => { 60 | this._refreshingPokemon = false; 61 | alert('An error occurred while refreshing Pokemon'); 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webapp/components/pokemon-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PokemonService } from '../services/pokemon.service' 4 | import { PropertiesService } from '../services/properties.service' 5 | import { UtilsService } from '../services/utils.service' 6 | import { SortService } from '../services/sort.service' 7 | import { SettingsService } from '../services/settings.service' 8 | 9 | import { Pokemon } from '../models/pokemon.model' 10 | import { Species } from '../models/species.model' 11 | import { Move } from '../models/move.model' 12 | 13 | import { SettingsComponent } from './settings.component' 14 | 15 | import { PrependZerosPipe } from '../pipes/prepend-zeros.pipe' 16 | 17 | @Component({ 18 | selector: 'pokemon-table', 19 | templateUrl: './webapp/templates/pokemon-table.component.html', 20 | styleUrls: ['./webapp/styles/pokemon-table.component.css'], 21 | directives: [SettingsComponent], 22 | pipes: [PrependZerosPipe] 23 | }) 24 | 25 | export class PokemonTableComponent { 26 | private _operatingOnPokemonAtIndex: number = null; 27 | private _operationName: string = null; 28 | private _retrieving = false; 29 | 30 | constructor( 31 | private _properties: PropertiesService, 32 | private _pokemonService: PokemonService, 33 | private _utils: UtilsService, 34 | private _sortService: SortService, 35 | private _settingsService: SettingsService 36 | ) {} 37 | 38 | private _getTableOutput(pokemon: Pokemon, property: string): string{ 39 | if(property === 'caught_time'){ 40 | let date = new Date(Number(pokemon.caught_time)); 41 | 42 | return date.getMonth()+1 + '/' + 43 | date.getDate() + '/' + 44 | date.getFullYear() + ' ' + 45 | this._utils.pad(date.getHours(), 2) + ':' + 46 | this._utils.pad(date.getMinutes(), 2) + ':' + 47 | this._utils.pad(date.getSeconds(), 2); 48 | 49 | } else if(property.includes('_dps')){ 50 | let moveSplit: string[] = property.split('_'); 51 | let moveType: string = moveSplit.length >= 1 ? moveSplit[0] : ''; 52 | 53 | if(moveType.toLowerCase() === 'total'){ 54 | let dps = 0; 55 | 56 | for(let i: number = 0; i < pokemon.moves.old_fast.length; i++){ 57 | let move: Move = pokemon.moves.old_fast[i]; 58 | 59 | if(move.selected){ 60 | dps += move.DPS; 61 | break; 62 | } 63 | } 64 | 65 | for(let i: number = 0; i < pokemon.moves.fast.length; i++){ 66 | let move: Move = pokemon.moves.fast[i]; 67 | 68 | if(move.selected){ 69 | dps += move.DPS; 70 | break; 71 | } 72 | } 73 | 74 | for(let i: number = 0; i < pokemon.moves.old_charged.length; i++){ 75 | let move: Move = pokemon.moves.old_charged[i]; 76 | 77 | if(move.selected){ 78 | dps += move.DPS; 79 | break; 80 | } 81 | } 82 | 83 | for(let i: number = 0; i < pokemon.moves.charged.length; i++){ 84 | let move: Move = pokemon.moves.charged[i]; 85 | 86 | if(move.selected){ 87 | dps += move.DPS; 88 | break; 89 | } 90 | } 91 | 92 | return dps.toFixed(2); 93 | } else { 94 | let moves: Move[] = pokemon.moves[moveType].concat(pokemon.moves['old_' + moveType]); 95 | 96 | for(let i: number = 0; i < moves.length; i++){ 97 | let move: Move = moves[i]; 98 | 99 | if(move.selected){ 100 | return 'Name: ' + move.name + '\n' + 101 | 'DPS: ' + move.DPS + '\n' + 102 | 'Type: ' + move.type + '\n' + 103 | 'STAB?: ' + move.givesStab; 104 | } 105 | } 106 | 107 | return ''; 108 | } 109 | } else if(property.includes('species_')){ 110 | let propertySplit: string[] = property.split('_'); 111 | let speciesProperty: string = propertySplit.length >= 2 ? propertySplit[1] : ''; 112 | let species: Species[] = this._pokemonService.species; 113 | 114 | for(let i: number = 0; i < species.length; i++){ 115 | let curSpecies = species[i]; 116 | 117 | if(curSpecies.pokedex_number === pokemon.pokedex_number){ 118 | return curSpecies[speciesProperty] === undefined ? '' : curSpecies[speciesProperty]; 119 | } 120 | } 121 | 122 | return ''; 123 | } else if(typeof pokemon[property] === 'boolean'){ 124 | return pokemon[property] ? '✓' : ''; 125 | } else { 126 | return pokemon[property] 127 | } 128 | 129 | } 130 | 131 | private _getTransferButtonText(index: number): string{ 132 | if(this._operatingOnPokemonAtIndex === index && this._operationName.toLowerCase() === 'transfer'){ 133 | return 'Transferring...'; 134 | } 135 | 136 | return 'Transfer'; 137 | } 138 | 139 | private _transferPokemon(pokemon: Pokemon, index: number){ 140 | let transfer = confirm('Are you sure that you want to transfer ' + 141 | pokemon.name + '?' + 142 | '\nCP: ' + pokemon.cp + 143 | '\nIV Percentage: ' + pokemon.iv_percentage + '%'); 144 | 145 | if(transfer){ 146 | this._operatingOnPokemonAtIndex = index; 147 | this._operationName = 'Transfer' 148 | 149 | this._pokemonService.transferPokemon(pokemon).then(() => { 150 | this._retrieving = true; 151 | this._pokemonService.retrievePokemon().then(this._retrievalComplete, this._handleError); 152 | }, this._handleError); 153 | } 154 | } 155 | 156 | private _getRenameButtonText(index: number): string{ 157 | if(this._operatingOnPokemonAtIndex === index && this._operationName.toLowerCase() === 'rename'){ 158 | return 'Renaming...'; 159 | } 160 | 161 | return 'Rename'; 162 | } 163 | 164 | private _renamePokemon(pokemon: Pokemon, index: number){ 165 | let nickname = prompt('Enter new nickname: '); 166 | 167 | if(nickname){ 168 | if(nickname.length > 12){ 169 | alert('Nicknames can be no longer than 12 characters. Sorry!'); 170 | } else { 171 | this._operatingOnPokemonAtIndex = index; 172 | this._operationName = 'Rename' 173 | 174 | this._pokemonService.renamePokemon(pokemon, nickname).then(() => { 175 | this._retrieving = true; 176 | this._pokemonService.retrievePokemon().then(this._retrievalComplete, this._handleError); 177 | }, this._handleError); 178 | } 179 | } 180 | } 181 | 182 | private _getFavoriteButtonText(index: number, isFavorite: boolean){ 183 | if(this._operatingOnPokemonAtIndex === index && this._operationName.toLowerCase() === 'favorite'){ 184 | return isFavorite ? 'Unfavoriting...' : 'Favoriting...'; 185 | } 186 | 187 | return isFavorite ? 'Unfavorite' : 'Favorite'; 188 | } 189 | 190 | private _toggleFavoritePokemon(pokemon: Pokemon, index: number) { 191 | this._operatingOnPokemonAtIndex = index; 192 | this._operationName = 'Favorite' 193 | 194 | this._pokemonService.toggleFavoritePokemon(pokemon).then(() => { 195 | this._retrieving = true; 196 | this._pokemonService.retrievePokemon().then(this._retrievalComplete, this._handleError); 197 | }, this._handleError); 198 | } 199 | 200 | private _handleError = () => { 201 | if(this._retrieving){ 202 | alert('Retrieval failed. Try clicking the reload icon next to the "Pokemon Stats" heading to get updated Pokemon.'); 203 | } else { 204 | alert(this._operationName + ' operation failed.'); 205 | } 206 | this._operatingOnPokemonAtIndex = null; 207 | this._operationName = null; 208 | } 209 | 210 | private _retrievalComplete = () => { 211 | if(this._sortService.pokemonSortOrder === ''){ 212 | this._sortService.sortPokemon(this._properties.defaultPokemonTableSortOrder, false); 213 | } else { 214 | this._sortService.sortPokemon(this._sortService.pokemonSortOrder, false); 215 | } 216 | 217 | this._operatingOnPokemonAtIndex = null; 218 | this._operationName = null; 219 | this._retrieving = false; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /webapp/components/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PropertiesService } from '../services/properties.service' 4 | import { SettingsService } from '../services/settings.service' 5 | import { SortService } from '../services/sort.service' 6 | 7 | @Component({ 8 | selector: 'settings', 9 | templateUrl: './webapp/templates/settings.component.html', 10 | styleUrls: ['./webapp/styles/settings.component.css'] 11 | }) 12 | 13 | export class SettingsComponent { 14 | constructor( 15 | private _properties: PropertiesService, 16 | private _settingsService: SettingsService, 17 | private _sortService: SortService 18 | ){ } 19 | } -------------------------------------------------------------------------------- /webapp/img/candy-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/candy-icon.png -------------------------------------------------------------------------------- /webapp/img/checkbox-empty-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/checkbox-empty-icon.png -------------------------------------------------------------------------------- /webapp/img/checkbox-filled-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/checkbox-filled-icon.png -------------------------------------------------------------------------------- /webapp/img/heart-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/heart-icon.png -------------------------------------------------------------------------------- /webapp/img/shield-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/shield-icon.png -------------------------------------------------------------------------------- /webapp/img/star-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/star-icon.png -------------------------------------------------------------------------------- /webapp/img/strength-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/strength-icon.png -------------------------------------------------------------------------------- /webapp/img/sword-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eric-Carlton/PokemonGoReader/ae1e06923194f2dc963819701a0d39eb83c891b8/webapp/img/sword-icon.png -------------------------------------------------------------------------------- /webapp/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from '@angular/platform-browser-dynamic'; 2 | import { HTTP_PROVIDERS } from '@angular/http'; 3 | import { disableDeprecatedForms, provideForms } from '@angular/forms'; 4 | 5 | import { AppComponent } from './components/app.component'; 6 | 7 | import { PokemonService } from './services/pokemon.service' 8 | import { PropertiesService } from './services/properties.service' 9 | import { UtilsService } from './services/utils.service' 10 | import { ExportService } from './services/export.service' 11 | import { SortService } from './services/sort.service' 12 | import { SettingsService } from './services/settings.service' 13 | 14 | bootstrap(AppComponent, [ 15 | disableDeprecatedForms(), 16 | provideForms(), 17 | HTTP_PROVIDERS, 18 | PokemonService, 19 | PropertiesService, 20 | UtilsService, 21 | ExportService, 22 | SortService, 23 | SettingsService 24 | ]) 25 | .catch((err: any) => console.error(err)); 26 | -------------------------------------------------------------------------------- /webapp/models/move-data.model.ts: -------------------------------------------------------------------------------- 1 | export class MoveData { 2 | constructor( 3 | public Id: number, 4 | public Name: string, 5 | public Speed: string, 6 | public Type: string, 7 | public Power: number, 8 | public DurationMs: number 9 | ){ } 10 | } 11 | -------------------------------------------------------------------------------- /webapp/models/move.model.ts: -------------------------------------------------------------------------------- 1 | export class Move { 2 | constructor( 3 | public type: string, 4 | public selected: boolean, 5 | public DPS: number, 6 | public name: string, 7 | public givesStab: boolean 8 | ){ } 9 | } 10 | -------------------------------------------------------------------------------- /webapp/models/pokemon-data.model.ts: -------------------------------------------------------------------------------- 1 | export class PokemonData { 2 | constructor( 3 | public Id : number, 4 | public Name: string, 5 | public BaseStamina: number, 6 | public BaseAttack : number, 7 | public BaseDefense : number, 8 | public Type1: string, 9 | public Type2: string, 10 | public QuickMoves: number[], 11 | public CinematicMoves: number[], 12 | public OldQuickMoves: number[], 13 | public OldCinematicMoves: number[], 14 | public CandyToEvolve: number, 15 | public FamilyId: number, 16 | public EvolutionIds: number[] 17 | ){ } 18 | } 19 | -------------------------------------------------------------------------------- /webapp/models/pokemon-table-stat.model.ts: -------------------------------------------------------------------------------- 1 | export class PokemonTableStat{ 2 | constructor( 3 | public property: string, 4 | public heading: string 5 | ) { } 6 | } -------------------------------------------------------------------------------- /webapp/models/pokemon.model.ts: -------------------------------------------------------------------------------- 1 | export class Pokemon { 2 | constructor( 3 | public pokedex_number : number, 4 | public name: string, 5 | public species: string, 6 | public attack_iv : number, 7 | public defense_iv : number, 8 | public stamina_iv : number, 9 | public current_hp: number, 10 | public max_hp: number, 11 | public iv_percentage: number, 12 | public cp: number, 13 | public favorite: boolean, 14 | public candy: number, 15 | public family_name: string, 16 | public id: number, 17 | public move_1: number, 18 | public move_2: number, 19 | public moves: any, 20 | public type_1: string, 21 | public type_2: string, 22 | public caught_time: string, 23 | public level: number 24 | ){ } 25 | } 26 | -------------------------------------------------------------------------------- /webapp/models/sort-order.model.ts: -------------------------------------------------------------------------------- 1 | import { SortType } from './sort-type.model' 2 | 3 | export class SortOrder{ 4 | constructor( 5 | public name: string, 6 | public sort_types: SortType[] 7 | ) { } 8 | } -------------------------------------------------------------------------------- /webapp/models/sort-type.model.ts: -------------------------------------------------------------------------------- 1 | export class SortType{ 2 | constructor( 3 | public property: string, 4 | public asc: boolean 5 | ) { } 6 | } -------------------------------------------------------------------------------- /webapp/models/species.model.ts: -------------------------------------------------------------------------------- 1 | export class Species { 2 | constructor( 3 | public pokedex_number : number, 4 | public species: string, 5 | public count: number, 6 | public candy: number, 7 | public evolve_sort: number, 8 | public evolve: any, 9 | public transfer: number, 10 | public need: number 11 | ){ } 12 | } 13 | -------------------------------------------------------------------------------- /webapp/models/user-login.model.ts: -------------------------------------------------------------------------------- 1 | export class UserLogin { 2 | constructor( 3 | public username : string, 4 | public password : string, 5 | public token : string, 6 | public type : string 7 | ){ } 8 | } 9 | -------------------------------------------------------------------------------- /webapp/pipes/prepend-zeros.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({name: 'prependZeros'}) 4 | export class PrependZerosPipe implements PipeTransform { 5 | transform(value: string): string { 6 | return("00"+ value ).slice(-3); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webapp/services/export.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { UtilsService } from '../services/utils.service' 4 | import { PokemonService } from '../services/pokemon.service' 5 | 6 | import { Pokemon } from '../models/pokemon.model'; 7 | import { Move } from '../models/move.model'; 8 | import { Species } from '../models/species.model'; 9 | import { PokemonTableStat } from '../models/pokemon-table-stat.model' 10 | 11 | @Injectable() 12 | export class ExportService { 13 | constructor( 14 | private _utils: UtilsService, 15 | private _pokemonService: PokemonService 16 | ) {} 17 | 18 | private _processMoves(moves: Move[]) { 19 | for (let moveIdx = 0; moveIdx < moves.length; moveIdx++) { 20 | let move = moves[moveIdx]; 21 | 22 | if (move.selected) { 23 | return move.name + ',' + move.DPS + ',' + move.type + ',' + move.givesStab; 24 | } 25 | } 26 | } 27 | 28 | public exportPokemon(stats: PokemonTableStat[]) { 29 | let output: string = ''; 30 | 31 | //get the headers for the CSV 32 | stats.forEach((stat, idx) => { 33 | let prop = stat.property.toLowerCase(); 34 | if (prop === 'fast_dps') { 35 | output += 'fast move name,fast move DPS,fast move type, fast move STAB'; 36 | } else if(prop === 'charged_dps'){ 37 | output += 'charge move name,charge move DPS,charge move type, charge move STAB'; 38 | } else if(prop === 'total_dps'){ 39 | output += 'total DPS' 40 | } else if(prop.includes('species_')){ 41 | let splitProp = prop.split('_'); 42 | if(splitProp.length >= 2) output += splitProp[1]; 43 | } else { 44 | output += prop; 45 | } 46 | 47 | if(idx < stats.length - 1) output += ','; 48 | }); 49 | 50 | output = output.replace(/(\_\w)/g, function(m) { 51 | return ' ' + m[1] 52 | }); 53 | 54 | // get each Pokemon's values for the CSV 55 | this._pokemonService.pokemon.forEach((mon) => { 56 | let matchingSpecies: Species = null; 57 | this._pokemonService.species.forEach((curSpecies) => { 58 | if(curSpecies.pokedex_number === mon.pokedex_number){ 59 | matchingSpecies = curSpecies; 60 | return; 61 | } 62 | }); 63 | 64 | output += '\n'; 65 | 66 | stats.forEach((stat, idx) => { 67 | let prop = stat.property.toLowerCase(); 68 | if (prop.includes('_dps')) { 69 | let moveSplit: string[] = prop.split('_'); 70 | let moveType: string = moveSplit.length >= 1 ? moveSplit[0] : ''; 71 | 72 | if(moveType === 'total'){ 73 | let dps = 0; 74 | 75 | mon.moves.old_fast.forEach((move) => { 76 | if(move.selected){ 77 | dps += move.DPS; 78 | return; 79 | } 80 | }); 81 | 82 | mon.moves.fast.forEach((move) => { 83 | if(move.selected){ 84 | dps += move.DPS; 85 | return; 86 | } 87 | }); 88 | 89 | mon.moves.old_charged.forEach((move) => { 90 | if(move.selected){ 91 | dps += move.DPS; 92 | return; 93 | } 94 | }); 95 | 96 | mon.moves.charged.forEach((move) => { 97 | if(move.selected){ 98 | dps += move.DPS; 99 | return; 100 | } 101 | }); 102 | 103 | output += dps.toFixed(2); 104 | } else { 105 | output += this._processMoves(mon.moves[moveType].concat(mon.moves['old_' + moveType])); 106 | } 107 | } else if(prop.includes('species_')){ 108 | let splitProp = prop.split('_'); 109 | output += matchingSpecies && splitProp.length >= 2 ? matchingSpecies[splitProp[1]] : 'N/A'; 110 | } else if(prop === 'caught_time'){ 111 | let date: Date = new Date(mon[prop]); 112 | 113 | output += date.getMonth()+1 + '/' + 114 | date.getDate() + '/' + 115 | date.getFullYear() + ' ' + 116 | this._utils.pad(date.getHours(), 2) + ':' + 117 | this._utils.pad(date.getMinutes(), 2) + ':' + 118 | this._utils.pad(date.getSeconds(), 2); 119 | } else { 120 | output += mon[prop]; 121 | } 122 | 123 | if(idx < stats.length - 1) output += ','; 124 | }); 125 | }); 126 | 127 | return encodeURIComponent(output); 128 | } 129 | } -------------------------------------------------------------------------------- /webapp/services/pokemon.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | import { Headers, Http } from '@angular/http' 3 | import 'rxjs/add/operator/toPromise' 4 | 5 | import { PokemonData } from '../models/pokemon-data.model' 6 | import { UserLogin } from '../models/user-login.model' 7 | import { MoveData } from '../models/move-data.model' 8 | import { Pokemon } from '../models/pokemon.model' 9 | import { Species } from '../models/species.model' 10 | import { Move } from '../models/move.model' 11 | 12 | import { PropertiesService } from './properties.service' 13 | 14 | @Injectable() 15 | export class PokemonService { 16 | private _pokemon : Pokemon[] = []; 17 | private _species: Species[] = []; 18 | private _userLogin: UserLogin = null; 19 | 20 | private _pokemonDataUrl = './data/pokemon.json'; 21 | private _moveDataUrl = './data/moves.json'; 22 | 23 | constructor( 24 | private _http: Http, 25 | private _properties: PropertiesService 26 | ) {} 27 | 28 | public get pokemon(): Pokemon[]{ 29 | return this._pokemon 30 | } 31 | 32 | public get species(): Species[]{ 33 | return this._species 34 | } 35 | 36 | public get userLogin(): UserLogin { 37 | return this._userLogin; 38 | } 39 | 40 | public set userLogin(userLogin: UserLogin){ 41 | this._userLogin = userLogin; 42 | } 43 | 44 | public retrievePokemon () { 45 | return this._http 46 | .get(this._pokemonDataUrl) 47 | .toPromise() 48 | .then(res => { 49 | let pokemonData = res.json(); 50 | 51 | return this._http 52 | .get(this._moveDataUrl) 53 | .toPromise() 54 | .then(res => { 55 | let moveData = res.json(); 56 | 57 | return this.retrievePokemonHelper(pokemonData, moveData); 58 | }); 59 | }); 60 | } 61 | 62 | public retrievePokemonHelper (pokemonData: PokemonData[], moveData: MoveData[]) { 63 | let headers = new Headers({'Content-Type': 'application/json'}); 64 | return this._http 65 | .post( 66 | this._properties.apiHost + this._properties.getPokemonRoute, 67 | JSON.stringify(this._userLogin), 68 | {headers: headers} 69 | ) 70 | .toPromise() 71 | .then(res => { 72 | let resBody = res.json(); 73 | 74 | this._pokemon = resBody.pokemon as Pokemon[]; 75 | this._pokemon = this._pokemon.map(function (pokemon) { 76 | pokemon.type_1 = pokemonData[pokemon.pokedex_number].Type1.toLowerCase(); 77 | pokemon.type_2 = pokemonData[pokemon.pokedex_number].Type2.toLowerCase(); 78 | 79 | pokemon.moves = { 80 | fast: pokemonData[pokemon.pokedex_number].QuickMoves.map(function (moveNumber: number) { 81 | let move: MoveData = moveData[moveNumber]; 82 | 83 | let givesStab: boolean = false; 84 | let dps: number = Number((move.Power / move.DurationMs * 1000).toFixed(2)); 85 | 86 | if(move.Type.toLowerCase() === pokemon.type_1 || move.Type.toLowerCase() === pokemon.type_2){ 87 | givesStab = true; 88 | dps = Number((dps * 1.25).toFixed(2)); 89 | } 90 | 91 | return new Move( 92 | move.Type.toLowerCase(), 93 | pokemon.move_1.toString() === moveNumber.toString(), 94 | dps, 95 | move.Name, 96 | givesStab 97 | ); 98 | }), 99 | charged: pokemonData[pokemon.pokedex_number].CinematicMoves.map(function (moveNumber: number) { 100 | let move: MoveData = moveData[moveNumber]; 101 | 102 | let givesStab: boolean = false; 103 | let dps: number = Number((move.Power / move.DurationMs * 1000).toFixed(2)); 104 | 105 | if(move.Type.toLowerCase() === pokemon.type_1 || move.Type.toLowerCase() === pokemon.type_2){ 106 | givesStab = true; 107 | dps = Number((dps * 1.25).toFixed(2)); 108 | } 109 | 110 | return new Move( 111 | move.Type.toLowerCase(), 112 | pokemon.move_2.toString() === moveNumber.toString(), 113 | dps, 114 | move.Name, 115 | givesStab 116 | ); 117 | }), 118 | old_fast: pokemonData[pokemon.pokedex_number].OldQuickMoves.map(function (moveNumber: number) { 119 | let move: MoveData = moveData[moveNumber]; 120 | 121 | let givesStab: boolean = false; 122 | let dps: number = Number((move.Power / move.DurationMs * 1000).toFixed(2)); 123 | 124 | if(move.Type.toLowerCase() === pokemon.type_1 || move.Type.toLowerCase() === pokemon.type_2){ 125 | givesStab = true; 126 | dps = Number((dps * 1.25).toFixed(2)); 127 | } 128 | 129 | return new Move( 130 | move.Type.toLowerCase(), 131 | pokemon.move_1.toString() === moveNumber.toString(), 132 | dps, 133 | move.Name, 134 | givesStab 135 | ); 136 | }), 137 | old_charged: pokemonData[pokemon.pokedex_number].OldCinematicMoves.map(function (moveNumber: number) { 138 | let move: MoveData = moveData[moveNumber]; 139 | 140 | let givesStab: boolean = false; 141 | let dps: number = Number((move.Power / move.DurationMs * 1000).toFixed(2)); 142 | 143 | if(move.Type.toLowerCase() === pokemon.type_1 || move.Type.toLowerCase() === pokemon.type_2){ 144 | givesStab = true; 145 | dps = Number((dps * 1.25).toFixed(2)); 146 | } 147 | 148 | return new Move( 149 | move.Type.toLowerCase(), 150 | pokemon.move_2.toString() === moveNumber.toString(), 151 | dps, 152 | move.Name, 153 | givesStab 154 | ); 155 | }) 156 | }; 157 | 158 | pokemon.moves.fast = pokemon.moves.fast.sort((a,b) => { 159 | if ( a.DPS < b.DPS ) return 1; 160 | if ( a.DPS > b.DPS) return -1; 161 | return 0; 162 | }); 163 | 164 | pokemon.moves.charged = pokemon.moves.charged.sort((a,b) => { 165 | if ( a.DPS < b.DPS ) return 1; 166 | if ( a.DPS > b.DPS) return -1; 167 | return 0; 168 | }); 169 | 170 | pokemon.moves.old_fast = pokemon.moves.old_fast.sort((a,b) => { 171 | if ( a.DPS < b.DPS ) return 1; 172 | if ( a.DPS > b.DPS) return -1; 173 | return 0; 174 | }); 175 | 176 | pokemon.moves.old_charged = pokemon.moves.old_charged.sort((a,b) => { 177 | if ( a.DPS < b.DPS ) return 1; 178 | if ( a.DPS > b.DPS) return -1; 179 | return 0; 180 | }); 181 | 182 | return pokemon; 183 | }); 184 | 185 | this._species = resBody.species as Species[]; 186 | this._userLogin.token = resBody.token; 187 | }) 188 | .catch(this.handleError); 189 | } 190 | 191 | public transferPokemon(pokemon: Pokemon){ 192 | let headers = new Headers({ 193 | 'Content-Type': 'application/json'}); 194 | 195 | let request = { 196 | username: this._userLogin.username, 197 | password: this._userLogin.password, 198 | type: this._userLogin.type, 199 | token: this._userLogin.token, 200 | id: pokemon.id 201 | }; 202 | 203 | return this._http 204 | .post( 205 | this._properties.apiHost + this._properties.transferPokemonRoute, 206 | JSON.stringify(request), 207 | {headers: headers} 208 | ) 209 | .toPromise() 210 | .then(res => { 211 | this._userLogin.token = res.json().token; 212 | }) 213 | .catch(this.handleError); 214 | } 215 | 216 | public renamePokemon(pokemon: Pokemon, nickname: string){ 217 | let headers = new Headers({ 218 | 'Content-Type': 'application/json'}); 219 | 220 | let request = { 221 | username: this._userLogin.username, 222 | password: this._userLogin.password, 223 | token: this._userLogin.token, 224 | type: this._userLogin.type, 225 | id: pokemon.id, 226 | nickname: nickname 227 | }; 228 | 229 | return this._http 230 | .post( 231 | this._properties.apiHost + this._properties.renamePokemonRoute, 232 | JSON.stringify(request), 233 | {headers: headers} 234 | ) 235 | .toPromise() 236 | .then(res => { 237 | this._userLogin.token = res.json().token; 238 | }) 239 | .catch(this.handleError); 240 | } 241 | 242 | public toggleFavoritePokemon(pokemon: Pokemon) { 243 | let headers = new Headers({ 244 | 'Content-Type': 'application/json'}); 245 | 246 | let request = { 247 | username: this._userLogin.username, 248 | password: this._userLogin.password, 249 | type: this._userLogin.type, 250 | token: this._userLogin.token, 251 | id: pokemon.id, 252 | favorite: !pokemon.favorite 253 | }; 254 | 255 | return this._http 256 | .post( 257 | this._properties.apiHost + this._properties.favoritePokemonRoute, 258 | JSON.stringify(request), 259 | {headers: headers} 260 | ) 261 | .toPromise() 262 | .then(res => { 263 | this._userLogin.token = res.json().token; 264 | }) 265 | .catch(this.handleError); 266 | } 267 | 268 | private handleError(error: any) { 269 | console.error('An error occurred', error); 270 | return Promise.reject(error.message || error); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /webapp/services/properties.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { SortType } from '../models/sort-type.model' 4 | import { SortOrder } from '../models/sort-order.model' 5 | import { PokemonTableStat } from '../models/pokemon-table-stat.model' 6 | 7 | import { UtilsService } from './utils.service' 8 | 9 | @Injectable() 10 | export class PropertiesService { 11 | constructor(private _utils: UtilsService){} 12 | 13 | public apiHost: string = '//' + window.location.hostname + ':8008'; 14 | public getPokemonRoute: string = '/api/pokemon/get'; 15 | public transferPokemonRoute: string = '/api/pokemon/transfer'; 16 | public renamePokemonRoute: string = '/api/pokemon/rename'; 17 | public favoritePokemonRoute: string = '/api/pokemon/favorite'; 18 | 19 | public loginComponentTitle: string = 'Pokemon Go! Pokemon Retriever'; 20 | public loginComponentContent: string = 'Why use an IV calculator when you can easily retrieve your Pokemon\'s exact data from Niantic? This easy-to-use tool allows you to do just that!'; 21 | public loginErrorMessage: string = 'Unable to login'; 22 | public loginTypes: string[] = this._utils.doesLocalStorageHaveItem('loginTypes') ? this._utils.getLocalStorageObj('loginTypes') : ['PTC', 'Google']; 23 | 24 | public pokemonStatsComponentTitle: string = 'Pokemon Stats'; 25 | public pokemonStatsComponentContent: string = 'Click a sort order to sort by that property. Default sort is Pokedex number, secondarily sorting by IV percentage where Pokedex number is the same, and finally sorting by CP where Pokedex number and IV percentage are the same.'; 26 | 27 | public useTabularFormat: boolean = false; 28 | public showTransferButton: boolean = true; 29 | public showRenameButton: boolean = true; 30 | public showFavoriteButton: boolean = true; 31 | public displayByMonster: boolean = true; 32 | 33 | public pokemonTableStats: PokemonTableStat[] = [ 34 | new PokemonTableStat('level', 'Level'), 35 | new PokemonTableStat('name', 'Name'), 36 | new PokemonTableStat('species', 'Species'), 37 | new PokemonTableStat('species_count', 'Count'), 38 | new PokemonTableStat('species_candy', 'Candy'), 39 | new PokemonTableStat('species_need', 'Need'), 40 | new PokemonTableStat('species_transfer', 'Transfer'), 41 | new PokemonTableStat('pokedex_number', 'Pokedex Number'), 42 | new PokemonTableStat('cp', 'CP'), 43 | new PokemonTableStat('max_hp', 'Max HP'), 44 | new PokemonTableStat('attack_iv', 'Attack IV'), 45 | new PokemonTableStat('defense_iv', 'Defense IV'), 46 | new PokemonTableStat('stamina_iv', 'Stamina IV'), 47 | new PokemonTableStat('iv_percentage', 'IV Percent'), 48 | new PokemonTableStat('fast_dps', 'Quick Move'), 49 | new PokemonTableStat('charged_dps', 'Charge Move'), 50 | new PokemonTableStat('total_dps', 'DPS'), 51 | new PokemonTableStat('favorite', 'Favorite'), 52 | new PokemonTableStat('caught_time', 'Caught Time') 53 | ]; 54 | 55 | public defaultPokemonTableSortOrder: string = 'pokedex_number'; 56 | public pokemonTableSortOrders: any = { 57 | level: new SortOrder( 58 | 'Level', [ 59 | new SortType('level', false), 60 | new SortType('pokedex_number', true), 61 | new SortType('iv_percentage', false), 62 | new SortType('cp', false)] 63 | ), 64 | 65 | name: new SortOrder( 66 | 'Name', [ 67 | new SortType('name', true), 68 | new SortType('iv_percentage', false), 69 | new SortType('cp', false)] 70 | ), 71 | 72 | species: new SortOrder( 73 | 'Species', [ 74 | new SortType('species', true), 75 | new SortType('iv_percentage', false), 76 | new SortType('cp', false)] 77 | ), 78 | 79 | pokedex_number: new SortOrder( 80 | 'Pokedex Number', [ 81 | new SortType('pokedex_number', true), 82 | new SortType('iv_percentage', false), 83 | new SortType('cp', false)] 84 | ), 85 | 86 | cp: new SortOrder( 87 | 'CP', [ 88 | new SortType('cp', false), 89 | new SortType('iv_percentage', false), 90 | new SortType('pokedex_number', true)] 91 | ), 92 | 93 | max_hp: new SortOrder( 94 | 'Max HP', [ 95 | new SortType('max_hp', false), 96 | new SortType('iv_percentage', false), 97 | new SortType('pokedex_number', true)] 98 | ), 99 | 100 | attack_iv: new SortOrder( 101 | 'Attack IV', [ 102 | new SortType('attack_iv', false), 103 | new SortType('iv_percentage', false), 104 | new SortType('pokedex_number', true)] 105 | ), 106 | 107 | defense_iv: new SortOrder( 108 | 'Defense IV', [ 109 | new SortType('defense_iv', false), 110 | new SortType('iv_percentage', false), 111 | new SortType('stamina_iv', false), 112 | new SortType('pokedex_number', true)] 113 | ), 114 | 115 | stamina_iv: new SortOrder( 116 | 'Stamina IV', [ 117 | new SortType('stamina_iv', false), 118 | new SortType('iv_percentage', false), 119 | new SortType('defense_iv', false), 120 | new SortType('pokedex_number', true)] 121 | ), 122 | 123 | iv_percentage: new SortOrder( 124 | 'IV Percent', [ 125 | new SortType('iv_percentage', false), 126 | new SortType('cp', false)] 127 | ), 128 | 129 | favorite: new SortOrder( 130 | 'Favorite', [ 131 | new SortType('favorite', false), 132 | new SortType('iv_percentage', false), 133 | new SortType('pokedex_number', true)] 134 | ), 135 | 136 | caught_time: new SortOrder('Caught Time', [new SortType('caught_time', false)]), 137 | 138 | fast_dps: new SortOrder( 139 | 'Quick Move DPS', [ 140 | new SortType('moves.fast.DPS', false), 141 | new SortType('cp', false), 142 | new SortType('iv_percentage', false), 143 | new SortType('pokedex_number', true)] 144 | ), 145 | 146 | charged_dps: new SortOrder( 147 | 'Charge Move DPS', [ 148 | new SortType('moves.charged.DPS', false), 149 | new SortType('cp', false), 150 | new SortType('iv_percentage', false), 151 | new SortType('pokedex_number', true)] 152 | ), 153 | 154 | total_dps: new SortOrder( 155 | 'Total DPS', [ 156 | new SortType('DPS', false), 157 | new SortType('cp', false), 158 | new SortType('iv_percentage', false), 159 | new SortType('pokedex_number', true)] 160 | ), 161 | 162 | species_count: new SortOrder( 163 | 'Count', [ 164 | new SortType('species_count', false), 165 | new SortType('pokedex_number', true)] 166 | ), 167 | 168 | species_candy: new SortOrder( 169 | 'Candy', [ 170 | new SortType('species_candy', false), 171 | new SortType('pokedex_number', true)] 172 | ), 173 | 174 | species_need: new SortOrder( 175 | 'Need', [ 176 | new SortType('species_need', false), 177 | new SortType('pokedex_number', true)] 178 | ), 179 | 180 | species_transfer: new SortOrder( 181 | 'Count', [ 182 | new SortType('species_count', false), 183 | new SortType('pokedex_number', true)] 184 | ) 185 | }; 186 | 187 | public defaultSpeciesSortOrder: string = 'pokedex_number'; 188 | public speciesSortOrders: any = { 189 | pokedex_number: new SortOrder('Pokedex Number', [new SortType('pokedex_number', true)]), 190 | 191 | species: new SortOrder('Species', [new SortType('species', true)]), 192 | 193 | count: new SortOrder( 194 | 'Count', [ 195 | new SortType('count', false), 196 | new SortType('pokedex_number', true)] 197 | ), 198 | 199 | candy: new SortOrder( 200 | 'Candy', [ 201 | new SortType('candy', false), 202 | new SortType('pokedex_number', true)] 203 | ), 204 | 205 | evolutions: new SortOrder( 206 | 'Evolutions', [ 207 | new SortType('evolve_sort', false), 208 | new SortType('pokedex_number', true)] 209 | ), 210 | 211 | transfer: new SortOrder( 212 | 'Transfer', [ 213 | new SortType('transfer', false), 214 | new SortType('pokedex_number', true)] 215 | ), 216 | 217 | need: new SortOrder( 218 | 'Need', [ 219 | new SortType('need', true), 220 | new SortType('pokedex_number', true)] 221 | ) 222 | }; 223 | } 224 | -------------------------------------------------------------------------------- /webapp/services/settings.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { PokemonTableStat } from '../models/pokemon-table-stat.model' 4 | 5 | import { PropertiesService } from './properties.service' 6 | import { UtilsService } from './utils.service' 7 | import { PokemonService } from './pokemon.service' 8 | 9 | @Injectable() 10 | export class SettingsService { 11 | constructor( 12 | private _properties: PropertiesService, 13 | private _utils: UtilsService, 14 | private _pokemonService: PokemonService 15 | ){ } 16 | 17 | public get displayByMonster(): boolean { 18 | return this.getUserSetting('displayByMonster'); 19 | } 20 | 21 | public get useTabularFormat(): boolean { 22 | return this.getUserSetting('useTabularFormat'); 23 | } 24 | 25 | public get showTransferButton(): boolean { 26 | return this.getUserSetting('showTransferButton'); 27 | } 28 | 29 | public get showRenameButton(): boolean { 30 | return this.getUserSetting('showRenameButton'); 31 | } 32 | 33 | public get showFavoriteButton(): boolean { 34 | return this.getUserSetting('showFavoriteButton'); 35 | } 36 | 37 | public get pokemonTableStats(): PokemonTableStat[] { 38 | return this.getUserSetting('pokemonTableStats'); 39 | } 40 | 41 | public set displayByMonster(displayByMonster: boolean) { 42 | this.saveUserSetting('displayByMonster', displayByMonster); 43 | } 44 | 45 | public set useTabularFormat(useTabularFormat: boolean) { 46 | this.saveUserSetting('useTabularFormat', useTabularFormat); 47 | } 48 | 49 | public set showTransferButton(showTransferButton: boolean) { 50 | this.saveUserSetting('showTransferButton', showTransferButton); 51 | } 52 | 53 | public set showRenameButton(showRenamButton: boolean) { 54 | this.saveUserSetting('showRenameButton', showRenamButton); 55 | } 56 | 57 | public set showFavoriteButton(showFavoriteButton: boolean) { 58 | this.saveUserSetting('showFavoriteButton', showFavoriteButton); 59 | } 60 | 61 | public createSettings(){ 62 | this._utils.setLocalStorageObj('settings', {}); 63 | } 64 | 65 | public createUserSettings() { 66 | let settings = this._utils.getLocalStorageObj('settings'); 67 | settings[this._pokemonService.userLogin.username.toLowerCase()] = {}; 68 | this._utils.setLocalStorageObj('settings', settings); 69 | } 70 | 71 | public saveUserSetting(setting: string, value: any) { 72 | let settings = this._utils.getLocalStorageObj('settings'); 73 | settings[this._pokemonService.userLogin.username.toLowerCase()][setting] = value; 74 | this._utils.setLocalStorageObj('settings', settings); 75 | } 76 | 77 | public getUserSetting(setting:string): any { 78 | if(this._utils.doesLocalStorageHaveItem('settings')){ 79 | let settings = this._utils.getLocalStorageObj('settings'); 80 | 81 | if(this._pokemonService.userLogin !== null){ 82 | if(settings.hasOwnProperty(this._pokemonService.userLogin.username.toLowerCase())){ 83 | let userSettings = settings[this._pokemonService.userLogin.username.toLowerCase()]; 84 | 85 | if(userSettings.hasOwnProperty(setting)){ 86 | return userSettings[setting]; 87 | } 88 | } else { 89 | this.createUserSettings(); 90 | } 91 | } 92 | } else { 93 | this.createSettings(); 94 | } 95 | 96 | return this._properties[setting]; 97 | } 98 | 99 | public isStatSelected(heading: string): boolean { 100 | let stats = this.getUserSetting('pokemonTableStats'); 101 | for(let idx = 0; idx < stats.length; idx++){ 102 | let stat = stats[idx]; 103 | if(stat.heading === heading) { 104 | return true; 105 | } 106 | } 107 | 108 | return false; 109 | } 110 | 111 | public statChanged(heading: string) { 112 | let userStats = this.getUserSetting('pokemonTableStats').slice(); 113 | 114 | if(this.isStatSelected(heading)){ 115 | for(let idx = 0; idx < userStats.length; idx++){ 116 | let userStat = userStats[idx]; 117 | 118 | if(userStat.heading === heading){ 119 | userStats.splice(idx, 1); 120 | break; 121 | } 122 | } 123 | } else { 124 | let allStats = this._properties.pokemonTableStats; 125 | let newUserStats = []; 126 | 127 | //this is gross, but I couldn't think of a better way to 128 | //retain order of headings 129 | for(let aidx = 0; aidx < allStats.length; aidx++){ 130 | let stat = allStats[aidx]; 131 | 132 | let statFound = false; 133 | 134 | for(let uidx = 0; uidx < userStats.length; uidx++){ 135 | if(userStats[uidx].heading === stat.heading){ 136 | statFound = true; 137 | break; 138 | } 139 | } 140 | 141 | if(statFound || stat.heading === heading){ 142 | newUserStats.push(stat) 143 | } 144 | } 145 | 146 | userStats = newUserStats; 147 | } 148 | 149 | this.saveUserSetting('pokemonTableStats', userStats); 150 | } 151 | } -------------------------------------------------------------------------------- /webapp/services/sort.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core' 2 | 3 | import { PropertiesService } from './properties.service' 4 | import { PokemonService } from '../services/pokemon.service' 5 | 6 | import { Pokemon } from '../models/pokemon.model' 7 | import { Species } from '../models/species.model' 8 | import { Move } from '../models/move.model' 9 | import { SortOrder } from '../models/sort-order.model' 10 | import { SortType } from '../models/sort-type.model' 11 | import { PokemonTableStat } from '../models/pokemon-table-stat.model' 12 | 13 | @Injectable() 14 | export class SortService { 15 | private _pokemonSortOrder: string = ''; 16 | private _speciesSortOrder: string = ''; 17 | 18 | constructor( 19 | private _properties: PropertiesService, 20 | private _pokemonService: PokemonService 21 | ){} 22 | 23 | public get pokemonSortOrder(): string { 24 | return this._pokemonSortOrder; 25 | } 26 | 27 | public get speciesSortOrder(): string { 28 | return this._speciesSortOrder; 29 | } 30 | 31 | public get pokemonSortOrders(): string[] { 32 | let allSortOrders: string[] = Object.keys(this._properties.pokemonTableSortOrders); 33 | let pokemonSortOrders: string[] = []; 34 | 35 | for(let i: number = 0; i < allSortOrders.length; i++){ 36 | let sortOrder: string = allSortOrders[i]; 37 | if(!sortOrder.includes('species_')) pokemonSortOrders.push(sortOrder); 38 | } 39 | 40 | return pokemonSortOrders; 41 | } 42 | 43 | public get speciesSortOrders(): string[] { 44 | return Object.keys(this._properties.speciesSortOrders); 45 | } 46 | 47 | public sortPokemon( 48 | sortOrderName: string, 49 | reverseSortOrder: boolean 50 | ) { 51 | if(this._properties.pokemonTableSortOrders.hasOwnProperty(sortOrderName)){ 52 | let sortOrder = this._properties.pokemonTableSortOrders[sortOrderName].sort_types; 53 | 54 | //double clicking a heading should reverse the primary sort 55 | if(this._pokemonSortOrder === sortOrderName && reverseSortOrder){ 56 | sortOrder[0].asc = !sortOrder[0].asc; 57 | } 58 | 59 | this._pokemonSortOrder = sortOrderName; 60 | 61 | this._pokemonService.pokemon.sort((a, b) => { 62 | for(let i: number = 0; i < sortOrder.length; i++){ 63 | let sortType: SortType = sortOrder[i]; 64 | 65 | //TODO: figure out a cleaner way of DPS sorting 66 | if(sortType.property === 'moves.fast.DPS'){ 67 | let moveA: Move = null; 68 | let moveB: Move = null; 69 | 70 | for(let moveIdx: number = 0; moveIdx < a.moves.fast.length; moveIdx++){ 71 | let move: Move = a.moves.fast[moveIdx]; 72 | if(move.selected){ 73 | moveA = move; 74 | break; 75 | } 76 | } 77 | 78 | for(let moveIdx: number = 0; moveIdx < b.moves.fast.length; moveIdx++){ 79 | let move: Move = b.moves.fast[moveIdx]; 80 | if(move.selected){ 81 | moveB = move; 82 | break; 83 | } 84 | } 85 | 86 | if(moveA && moveB && moveA.DPS < moveB.DPS) return sortType.asc ? -1 : 1; 87 | if(moveA && moveB && moveA.DPS > moveB.DPS) return sortType.asc ? 1 : -1; 88 | } else if(sortType.property === 'moves.charged.DPS'){ 89 | let moveA: Move = null; 90 | let moveB: Move = null; 91 | 92 | for(let moveIdx: number = 0; moveIdx < a.moves.charged.length; moveIdx++){ 93 | let move: Move = a.moves.charged[moveIdx]; 94 | if(move.selected){ 95 | moveA = move; 96 | break; 97 | } 98 | } 99 | 100 | for(let moveIdx: number = 0; moveIdx < b.moves.charged.length; moveIdx++){ 101 | let move: Move = b.moves.charged[moveIdx]; 102 | if(move.selected){ 103 | moveB = move; 104 | break; 105 | } 106 | } 107 | 108 | if(moveA && moveB && moveA.DPS < moveB.DPS) return sortType.asc ? -1 : 1; 109 | if(moveA && moveB && moveA.DPS > moveB.DPS) return sortType.asc ? 1 : -1; 110 | } else if(sortType.property === 'DPS') { 111 | let dpsA: number = 0; 112 | let dpsB: number = 0; 113 | 114 | for(let moveIdx: number = 0; moveIdx < a.moves.fast.length; moveIdx++){ 115 | let move: Move = a.moves.fast[moveIdx]; 116 | if(move.selected){ 117 | dpsA += move.DPS; 118 | break; 119 | } 120 | } 121 | 122 | for(let moveIdx: number = 0; moveIdx < b.moves.fast.length; moveIdx++){ 123 | let move: Move = b.moves.fast[moveIdx]; 124 | if(move.selected){ 125 | dpsB += move.DPS; 126 | break; 127 | } 128 | } 129 | 130 | for(let moveIdx: number = 0; moveIdx < a.moves.charged.length; moveIdx++){ 131 | let move: Move = a.moves.charged[moveIdx]; 132 | if(move.selected){ 133 | dpsA += move.DPS; 134 | break; 135 | } 136 | } 137 | 138 | for(let moveIdx: number = 0; moveIdx < b.moves.charged.length; moveIdx++){ 139 | let move: Move = b.moves.charged[moveIdx]; 140 | if(move.selected){ 141 | dpsB += move.DPS; 142 | break; 143 | } 144 | } 145 | 146 | if(dpsA < dpsB) return sortType.asc ? -1 : 1; 147 | if(dpsA > dpsB) return sortType.asc ? 1 : -1; 148 | } else if(sortType.property.includes('species_')){ 149 | let propertySplit: string[] = sortType.property.split('_'); 150 | let speciesProperty: string = propertySplit.length > 0 ? propertySplit[1] : ''; 151 | 152 | let speciesA: Species = null; 153 | let speciesB: Species = null; 154 | 155 | for(let speciesIdx: number = 0; speciesIdx < this._pokemonService.species.length; speciesIdx++){ 156 | let curSpecies = this._pokemonService.species[speciesIdx]; 157 | 158 | if(a.pokedex_number === curSpecies.pokedex_number) speciesA = curSpecies; 159 | if(b.pokedex_number === curSpecies.pokedex_number) speciesB = curSpecies; 160 | 161 | if(speciesA !== null && speciesB !== null) break; 162 | } 163 | 164 | if(speciesA[speciesProperty] < speciesB[speciesProperty]) return sortType.asc ? -1 : 1; 165 | if(speciesA[speciesProperty] > speciesB[speciesProperty]) return sortType.asc ? 1 : -1; 166 | } else { 167 | if(a[sortType.property] < b[sortType.property]) return sortType.asc ? -1 : 1; 168 | if(a[sortType.property] > b[sortType.property]) return sortType.asc ? 1 : -1; 169 | } 170 | } 171 | return 0; 172 | }); 173 | } 174 | } 175 | 176 | public sortSpecies( 177 | sortOrderName: string, 178 | reverseSortOrder: boolean 179 | ) { 180 | if(this._properties.speciesSortOrders.hasOwnProperty(sortOrderName)){ 181 | let sortOrder = this._properties.speciesSortOrders[sortOrderName].sort_types; 182 | 183 | //double clicking a heading should reverse the primary sort 184 | if(this._speciesSortOrder === sortOrderName && reverseSortOrder){ 185 | sortOrder[0].asc = !sortOrder[0].asc; 186 | } 187 | 188 | this._speciesSortOrder = sortOrderName; 189 | 190 | this._pokemonService.species.sort((a, b) => { 191 | for(let i: number = 0; i < sortOrder.length; i++){ 192 | let sortType: SortType = sortOrder[i]; 193 | 194 | if(a[sortType.property] < b[sortType.property]) return sortType.asc ? -1 : 1; 195 | if(a[sortType.property] > b[sortType.property]) return sortType.asc ? 1 : -1; 196 | } 197 | return 0; 198 | }); 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /webapp/services/utils.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class UtilsService { 5 | public pad(num: number, size: number): string { 6 | let s = num+''; 7 | while(s.length < size) s = '0' + s; 8 | return s; 9 | } 10 | 11 | public doesLocalStorageHaveItem(key: string): boolean { 12 | return localStorage.getItem(key) !== null; 13 | } 14 | 15 | public setLocalStorageObj(key: string, obj: any) { 16 | localStorage.setItem(key, JSON.stringify(obj)); 17 | } 18 | 19 | public getLocalStorageObj(key: string): any { 20 | return JSON.parse(localStorage.getItem(key)); 21 | } 22 | 23 | public setLocalStorageBool(key: string, bool: boolean) { 24 | localStorage.setItem(key, bool.toString()); 25 | } 26 | 27 | public getLocalStorageBool(key: string): boolean { 28 | return localStorage.getItem(key).toLowerCase() === 'true'; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /webapp/styles/global.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto:300,400,500'; 2 | 3 | body { 4 | margin: 0 auto; 5 | max-width: 97em; 6 | padding: 4em 1em; 7 | line-height: 1.5; 8 | font-family: "Roboto", "Helvetica", "Arial", sans-serif; 9 | color: #555; 10 | background-color: #f7f7f7; 11 | } 12 | 13 | h1 { 14 | font-size: 18px; 15 | } 16 | 17 | p { 18 | text-align: center; 19 | } 20 | 21 | .error { 22 | color: red; 23 | font-weight: bold; 24 | } 25 | 26 | .dropdown-menu { 27 | text-align: center; 28 | min-width: 120px; 29 | } 30 | 31 | .dropdown-menu li { 32 | cursor: pointer; 33 | } 34 | 35 | .dropdown-menu li:hover { 36 | background-color:#dedede; 37 | } 38 | 39 | .bold { 40 | font-weight: bold; 41 | } 42 | 43 | .text-left { 44 | text-align: left; 45 | } 46 | 47 | .text-center { 48 | text-align: center; 49 | } 50 | 51 | .text-right { 52 | text-align: right; 53 | } 54 | 55 | .text-xs { 56 | font-size: .75em; 57 | } 58 | 59 | .text-sm { 60 | font-size: .85em; 61 | } 62 | 63 | [hidden] { 64 | display: none!important; 65 | } -------------------------------------------------------------------------------- /webapp/styles/login-form.component.css: -------------------------------------------------------------------------------- 1 | form{ 2 | margin: 1.5em auto; 3 | text-align: center; 4 | } 5 | 6 | form div.form-group { 7 | margin-top: .5em; 8 | } 9 | 10 | form div.form-group label { 11 | width: 5.5em; 12 | } 13 | 14 | form button { 15 | margin-top: .5em; 16 | } -------------------------------------------------------------------------------- /webapp/styles/login.component.css: -------------------------------------------------------------------------------- 1 | #login-component h1{ 2 | margin: 1.5em auto; 3 | text-align: center; 4 | } -------------------------------------------------------------------------------- /webapp/styles/pokemon-species.component.css: -------------------------------------------------------------------------------- 1 | .dropdown{ 2 | text-align:center; 3 | } 4 | 5 | .dropdown-menu{ 6 | width:150px; 7 | left:50%; 8 | margin-left:-75px; 9 | } 10 | 11 | .sortby-wrapper { 12 | text-align: center; 13 | margin-top: 1.5em; 14 | } 15 | 16 | .sortby-item { 17 | cursor: pointer; 18 | display: inline-block; 19 | padding: .25em .5em; 20 | border: 1px solid black; 21 | margin: 3px; 22 | font-size: .99em; 23 | } 24 | 25 | .pokemon-list-wrapper { 26 | text-align: center; 27 | } 28 | 29 | .pokemon-list-item { 30 | position: relative; 31 | border: 1px solid #f0f0f0; 32 | background: white; 33 | box-shadow: 2px 2px 3px rgba(0,0,0,0.1); 34 | margin: 3px; 35 | display: inline-block; 36 | width: 180px; 37 | height: 150; 38 | padding: .5em; 39 | overflow: hidden; 40 | } 41 | 42 | .pokemon-list-item .icon { 43 | position: relative; 44 | right: 2em; 45 | } 46 | 47 | .pokemon-list-item .candies { 48 | position: absolute; 49 | top: .5em; 50 | right: .5em; 51 | font-size: .9em; 52 | } 53 | 54 | .pokemon-list-item .candies img { 55 | display: block; 56 | } 57 | 58 | #sort-orders{ 59 | margin-bottom: 1.5em; 60 | } 61 | -------------------------------------------------------------------------------- /webapp/styles/pokemon-stats.component.css: -------------------------------------------------------------------------------- 1 | .glyphicon-refresh { 2 | cursor: pointer 3 | } 4 | 5 | #pokemon-stats-component h1{ 6 | text-align: center; 7 | } 8 | 9 | #pokemon-stats-component section { 10 | margin: 1.5em auto; 11 | text-align: center; 12 | } 13 | 14 | #title-heading{ 15 | display: inline-block; 16 | margin: auto 1em; 17 | } 18 | 19 | #csvDownloadLink { 20 | color: #555; 21 | cursor: pointer; 22 | } -------------------------------------------------------------------------------- /webapp/styles/pokemon-table.component.css: -------------------------------------------------------------------------------- 1 | table#stats-as-table thead tr{ 2 | position: relative; 3 | display: block; 4 | } 5 | 6 | table#stats-as-table tbody{ 7 | display: block; 8 | overflow-y: scroll; 9 | height: 600px; 10 | } 11 | 12 | 13 | table#stats-as-table th, table#stats-as-table td { 14 | padding: .5em; 15 | max-width: 14em; 16 | min-width: 14em; 17 | } 18 | 19 | table#stats-as-table td pre{ 20 | display: block; 21 | padding: 0; 22 | margin: 0; 23 | font-size: inherit; 24 | font-family: inherit; 25 | line-height: inherit; 26 | color: inherit; 27 | word-break: break-all; 28 | word-wrap: break-word; 29 | background-color: inherit; 30 | border: none; 31 | border-radius: 0px; 32 | } 33 | 34 | table#stats-as-table th { 35 | cursor: pointer; 36 | } 37 | 38 | .table-responsive { 39 | width:100%; 40 | margin: 1.5em auto; 41 | } 42 | 43 | .dropdown-menu { 44 | width: 150px; 45 | left: 50%; 46 | margin-left: -108px; 47 | } 48 | 49 | .pokemon-list { 50 | padding: 0; 51 | } 52 | 53 | .pokemon-list-item { 54 | height: 360px; 55 | padding: 0 5px; 56 | } 57 | 58 | .pokemon-list-item-content { 59 | height: 350px; 60 | background-color: white; 61 | position: relative; 62 | } 63 | 64 | .pokemon-overview > span, .pokemon-overview > div , .pokemon-overview > div > span{ 65 | padding: 0; 66 | } 67 | 68 | .pokemon-overview > div > span { 69 | margin-bottom: 14px; 70 | } 71 | 72 | .pokemon-overview > div > img { 73 | height: 15px; 74 | width: 15px; 75 | } 76 | 77 | .pokemon-ivs > div { 78 | height: 30px; 79 | } 80 | 81 | .pokemon-ivs > div > span { 82 | padding: 0 5px 0 0; 83 | } 84 | 85 | .pokemon-ivs > div > img { 86 | padding: 0; 87 | } 88 | 89 | .pokemon-moves { 90 | margin: 5px 0; 91 | } 92 | 93 | .pokemon-moves > .row > span { 94 | padding: 0; 95 | } 96 | 97 | .selected-icon { 98 | height: 10px; 99 | width: 10px; 100 | } 101 | 102 | .pokemon-actions { 103 | position: absolute; 104 | bottom: 0; 105 | width: 100%; 106 | margin: 10px auto; 107 | } -------------------------------------------------------------------------------- /webapp/styles/settings.component.css: -------------------------------------------------------------------------------- 1 | .settings-container { 2 | margin-bottom: 20px; 3 | } 4 | 5 | .row > section { 6 | padding: 0; 7 | } 8 | 9 | #sort-orders .dropdown-menu, #table-settings .dropdown-menu { 10 | right: 0; 11 | } 12 | 13 | @media (min-width: 768px) { 14 | #settings .dropdown-menu { 15 | left: 12%; 16 | } 17 | } 18 | 19 | @media (min-width: 1200px) { 20 | #settings .dropdown-menu { 21 | left: 23%; 22 | } 23 | } -------------------------------------------------------------------------------- /webapp/templates/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webapp/templates/login-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 8 |
9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 |
24 | 25 | 26 |
Loading...
27 |
{{_loginErrorMessage}}
28 |
-------------------------------------------------------------------------------- /webapp/templates/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{_title}}

3 |

{{_content}}

4 | 5 |
6 | -------------------------------------------------------------------------------- /webapp/templates/pokemon-species.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | {{ species.species }}
7 | Count: {{ species.count }} 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
{{ species.candy }}
{{ descendant.canEvolve }}
{{ species.transfer }}
{{ species.need }}
27 |
28 |
29 | -------------------------------------------------------------------------------- /webapp/templates/pokemon-stats.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

{{_title}}

6 | 7 |
8 |

{{_content}}

9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /webapp/templates/pokemon-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 21 | 24 | 27 | 28 | 29 |
{{stat.heading}}TransferRenameFavorite
16 |
{{_getTableOutput(mon, stat.property)}}
17 |
19 | 20 | 22 | 23 | 25 | 26 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
IV {{ mon.iv_percentage | number:'.0-0' }}%
41 |
HP {{mon.current_hp}} / {{mon.max_hp}}
42 |
{{ mon.candy }} 43 |
44 | 45 |
46 |
CP {{ mon.cp }}
47 |
Level {{ mon.level }}
48 | 50 |
{{ mon.name }}
51 | 52 | 53 |
54 | 55 |
56 |
57 | 58 | {{ mon.attack_iv }} 59 |
60 |
61 | 62 | {{ mon.defense_iv }} 63 |
64 |
65 | 66 | {{ mon.stamina_iv }} 67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 | 75 | 76 | 77 | 78 | 79 | {{ atk.name }} 80 | {{ atk.DPS | number : '1.2-2' }} DPS 81 |
82 |
83 | 84 | 85 | 86 | 87 | {{ atk.name }} (Old) 88 | {{ atk.DPS | number : '1.2-2' }} DPS 89 |
90 |
91 | 92 |
93 |
94 | 95 | 96 | 97 | 98 | 99 | {{ atk.name }} 100 | {{ atk.DPS | number : '1.2-2' }} DPS 101 |
102 |
103 | 104 | 105 | 106 | 107 | {{ atk.name }} (Old) 108 | {{ atk.DPS | number : '1.2-2' }} DPS 109 |
110 |
111 | 112 |
113 | 115 | 117 | 119 |
120 |
121 |
122 |
-------------------------------------------------------------------------------- /webapp/templates/settings.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 14 | 27 | 37 | 49 |
50 |
--------------------------------------------------------------------------------