diff --git a/.eslintrc.json b/.eslintrc.json index 08069c84bb..379afb5988 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,6 +23,7 @@ "setupPokemonMarker": true, "isTouchDevice": true, + "isMobileDevice": true, "pokemonSprites": true, "noLabelsStyle": true, "darkStyle": true, diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 903c6adb52..555dbe708c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,6 +23,9 @@ ## Checklist: + + + - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. diff --git a/.gitignore b/.gitignore index c8d77ecac4..bce3db64fd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ beehive.* !Tools/Easy-Setup/setup.bat !contrib/supervisord/gen-workers.sh !contrib/supervisord/install-reinstall.sh +!static/data/* \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index dd1532c432..3f38ecc831 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -60,6 +60,7 @@ module.exports = function(grunt) { build: { files: { 'static/dist/data/pokemon.min.json': 'static/data/pokemon.json', + 'static/dist/data/moves.min.json': 'static/data/moves.json', 'static/dist/data/mapstyle.min.json': 'static/data/mapstyle.json', 'static/dist/data/searchmarkerstyle.min.json': 'static/data/searchmarkerstyle.json', 'static/dist/locales/de.min.json': 'static/locales/de.json', diff --git a/Tools/Easy-Setup/easy-setup.sh b/Tools/Easy-Setup/easy-setup.sh index d3ed5f8518..fc8b30aa6a 100755 --- a/Tools/Easy-Setup/easy-setup.sh +++ b/Tools/Easy-Setup/easy-setup.sh @@ -8,7 +8,11 @@ echo "" if [ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]; then echo "Installing python development tools..." - sudo apt-get install python python-dev + sudo apt-get install python python-dev build-essential -y + echo "Adding node repo..." + curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - + echo "Installing node..." + sudo apt-get install nodejs -y else echo "This script only supports debain based Linux distros." echo "Please install manually." @@ -18,7 +22,11 @@ fi echo "Installing pip..." sudo python get-pip.py echo "Installing required python packages..." -pip install -r $scriptDir/../../requirements.txt +sudo pip install -r $scriptDir/../../requirements.txt --upgrade +echo "Installing frontend dependencies..." +npm --prefix $scriptDir/../../ install $scriptDir/../../ +echo "Building frontend..." +npm run build echo "Configuring Google Maps API..." cp $scriptDir/../../config/config.ini.example $scriptDir/../../config/config.ini diff --git a/Tools/Easy-Setup/setup.bat b/Tools/Easy-Setup/setup.bat index 7e0273663c..d297e69765 100644 --- a/Tools/Easy-Setup/setup.bat +++ b/Tools/Easy-Setup/setup.bat @@ -1,4 +1,5 @@ @echo off +echo Requesting Administrator Access... pushd %~dp0 :: Running prompt elevated :------------------------------------- @@ -22,23 +23,55 @@ if '%errorlevel%' NEQ '0' ( pushd "%CD%" CD /D "%~dp0" :-------------------------------------- +echo. +echo Setting PATHs... +echo. IF EXIST C:\Python27 ( set PATH2=C:\Python27 ) ELSE ( echo Python path not found, please specify or install. +echo. set /p PATH2= Specify Python path: ) + +for /f "tokens=*" %%i in ('echo "%PATH%" ^| find /c /i "%PATH2%"') do set output=%%i +if %output% equ 1 ( +goto found +) else ( +goto notfound +) + +:found +cls +echo Path is already set, skipping... +goto Continue + +:notfound +cls setx PATH "%PATH%;%PATH2%;%PATH2%\Scripts;" +echo. +echo Path set, continuing.. +goto Continue + + +:Continue popd +echo. +echo Installing requirements... +echo. + "%PATH2%\python" get-pip.py cd ..\.. "%PATH2%\Scripts\pip" install -r requirements.txt "%PATH2%\Scripts\pip" install -r requirements.txt --upgrade +call npm install +call npm run build + cd config set /p API= Enter your Google API key here: "%PATH2%\python" -c "print open('config.ini.example').read().replace('#gmaps-key:','gmaps-key:%API%')" > config.ini diff --git a/config/config.ini.example b/config/config.ini.example index ba2e9c6ea3..0f11c49e92 100644 --- a/config/config.ini.example +++ b/config/config.ini.example @@ -23,6 +23,12 @@ #min-seconds-left: # time that must be left on a spawn before considering it too late and skipping it (default 0) #status-name: # enables writing status updates to the database - if you use multiple processes, each needs a unique value +#Pokemon IV +#encounter: # Set to true to start encounters to pull more info, like IVs or movesets. (default false) +#encounter-delay: # delay before starting an encounter. Must not be zero. (default 1) +#encounter-whitelist: # whitelist of pokemon ids to encounter. Syntax [id,id,id,id] (Do not use with blacklist) +#encounter-blacklist: # blacklist of pokemon ids to NOT encounter. Syntax [id,id,id,id] (Do not use with whitelist) + # Misc #gmaps-key: # your Google Maps API key #proxy: # Proxy URL e.g. socks5://127.0.0.1:9050 or a list of proxies e.g. [socks5://127.0.0.1:9050,socks5://127.0.0.1:9050] @@ -41,4 +47,5 @@ #status-page-password: # enables and protects the /status page to view status of all workers #Uncomment a line when you want to change its default value (Remove # at the beginning) +#Please ensure to leave a space after the : (example setting: value) #username, password, location and gmaps-key are required diff --git a/docs/advanced-install/amazon-ecs.md b/docs/advanced-install/amazon-ecs.md index 53ab6098f4..64b34c303b 100644 --- a/docs/advanced-install/amazon-ecs.md +++ b/docs/advanced-install/amazon-ecs.md @@ -2,7 +2,7 @@ > **Warning** -- Most cloud providers have been IP blocked from accessing the API -Amazon ECS is essentially managed docker allowed you to run multi-container environments easily with minimal configuration. In this guide we'll create an ECS Task that will run a single pokemongo-map container with a MariaDB container +Amazon ECS is essentially managed docker allowed you to run multi-container environments easily with minimal configuration. In this guide we'll create an ECS Task that will run a single pokemongo-map container with a MariaDB container for persisting the data ## Requirements @@ -14,12 +14,12 @@ Amazon ECS is essentially managed docker allowed you to run multi-container envi In the AWS ECS console create a Task Definition with the JSON below. You will need to set the following values: -* `POKEMON_USERNAME` - username for pokemongo -* `POKEMON_PASSWORD` - password for pokemongo -* `POKEMON_AUTH_SERVICE` - Define if you are using google or ptc auth -* `POKEMON_LOCATION` - Location to search -* `POKEMON_DB_USER` - Database user for MariaDB -* `POKEMON_DB_PASS` - Database password for MariaDB +* `POGOM_USERNAME` - username for pokemongo +* `POGOM_PASSWORD` - password for pokemongo +* `POGOM_AUTH_SERVICE` - Define if you are using google or ptc auth +* `POGOM_LOCATION` - Location to search +* `POGOM_DB_USER` - Database user for MariaDB +* `POGOM_DB_PASS` - Database password for MariaDB ```json { @@ -48,51 +48,47 @@ In the AWS ECS console create a Task Definition with the JSON below. You will ne "dockerSecurityOptions": null, "environment": [ { - "name": "POKEMON_DB_TYPE", + "name": "POGOM_DB_TYPE", "value": "mysql" }, { - "name": "POKEMON_LOCATION", + "name": "POGOM_LOCATION", "value": "Seattle, WA" }, { - "name": "POKEMON_DB_HOST", + "name": "POGOM_DB_HOST", "value": "database" }, { - "name": "POKEMON_NUM_THREADS", + "name": "POGOM_NUM_THREADS", "value": "1" }, { - "name": "POKEMON_DB_NAME", + "name": "POGOM_DB_NAME", "value": "pogom" }, { - "name": "POKEMON_PASSWORD", + "name": "POGOM_PASSWORD", "value": "MyPassword" }, { - "name": "POKEMON_GMAPS_KEY", + "name": "POGOM_GMAPS_KEY", "value": "SUPERSECRET" }, { - "name": "POKEMON_AUTH_SERVICE", + "name": "POGOM_AUTH_SERVICE", "value": "ptc" }, { - "name": "POKEMON_DB_PASS", + "name": "POGOM_DB_PASS", "value": "somedbpassword" }, { - "name": "POKEMON_DB_USER", + "name": "POGOM_DB_USER", "value": "pogom" }, { - "name": "POKEMON_STEP_LIMIT", - "value": "10" - }, - { - "name": "POKEMON_USERNAME", + "name": "POGOM_USERNAME", "value": "MyUser" } ], @@ -101,7 +97,7 @@ In the AWS ECS console create a Task Definition with the JSON below. You will ne ], "workingDirectory": null, "readonlyRootFilesystem": null, - "image": "ashex/pokemongo-map", + "image": "frostthefox/pokemongo-map", "command": null, "user": null, "dockerLabels": null, @@ -160,6 +156,6 @@ In the AWS ECS console create a Task Definition with the JSON below. You will ne ``` -If you would like to add workers you can easily do so by adding another container with the additional variable `POKEMON_NO_SERVER` set to `true`. You have to let one of the pokemongo-map containers start first to create the database, an easy way to control this is to create a link from the worker to the primary one as it will delay the start. +If you would like to add workers you can easily do so by adding another container with the additional variable `POGOM_NO_SERVER` set to `true`. You have to let one of the pokemongo-map containers start first to create the database, an easy way to control this is to create a link from the worker to the primary one as it will delay the start. -Once the Task is running you'll be able to access the app via the Instances IP on port 80. \ No newline at end of file +Once the Task is running you'll be able to access the app via the Instances IP on port 80. diff --git a/docs/advanced-install/docker.md b/docs/advanced-install/docker.md index 450a21e2e5..2b1c1f9706 100644 --- a/docs/advanced-install/docker.md +++ b/docs/advanced-install/docker.md @@ -22,14 +22,14 @@ If that doesn't bother you, and you just want to give PokemonGo-Map a go, keep o In order to start the map, you've got to run your docker container with a few arguments, such as authentication type, account, password, desired location and steps. If you don't know which arguments are necessary, you can use the following command to get help: ``` -docker run --rm pokemap/pokemongo-map -h +docker run --rm frostthefox/pokemongo-map -h ``` To be able to access the map in your machine via browser, you've got to bind a port on your host machine to the one wich will be exposed by the container (default is 5000). The following docker run command is an example of to launch a container with a very basic setup of the map, following the instructions above: ``` docker run -d --name pogomap -p 5000:5000 \ - pokemap/pokemongo-map \ + frostthefox/pokemongo-map \ -a ptc -u username -p password \ -k 'your-google-maps-key' \ -l 'lat, lon' \ @@ -94,24 +94,23 @@ Open that URL in your browser and you're ready to rock! In order to update your PokemonGo-Map docker image, you should stop/remove all the containers running with the current (outdated) version (refer to "Stopping the server"), pull the latest docker image version, and restart everything. To pull the latest image, use the following command: ``` -docker pull pokemap/pokemongo-map +docker pull frostthefox/pokemongo-map ``` If you are running a ngrok container, you've got to stop it as well. To start the server after updating your image, simply use the same commands that were used before, and the containers will be launched with the latest version. ## Running on docker cloud -If you want to run pokemongo-map on a service that doesn't support arguments like docker cloud or ECS, you'll need to use one of the more specialised images out there that supports variables. The image `ashex/pokemongo-map` handles variables, below is an example: +If you want to run pokemongo-map on a service that doesn't support arguments like docker cloud or ECS, you'll need to pass settings via variables below is an example: ```bash docker run -d -P \ - -e "AUTH_SERVICE=ptc" \ - -e "USERNAME=UserName" \ - -e "PASSWORD=Password" \ - -e "LOCATION=Seattle, WA" \ - -e "STEP_LIMIT=5" \ - -e "GMAPS_KEY=SUPERSECRET" \ - ashex/pokemongo-map + -e "POGOM_AUTH_SERVICE=ptc" \ + -e "POGOM_USERNAME=UserName" \ + -e "POGOM_PASSWORD=Password" \ + -e "POGOM_LOCATION=Seattle, WA" \ + -e "POGOM_GMAPS_KEY=SUPERSECRET" \ + frostthefox/pokemongo-map ``` ## Advanced Docker Setup @@ -162,7 +161,7 @@ Now that we have a persistent database up and running, we need to launch our Pok ``` docker run -d --name pogomap --net=pogonw -p 5000:5000 \ - pokemap/pokemongo-map \ + frostthefox/pokemongo-map \ -a ptc -u username -p password \ -k 'your-google-maps-key' \ -l 'lat, lon' \ @@ -191,7 +190,7 @@ If you would like to launch a different worker sharing the same db, to scan a di ``` docker run -d --name pogomap2 --net=pogonw \ - pokemap/pokemongo-map \ + frostthefox/pokemongo-map \ -a ptc -u username2 -p password2 \ -k 'your-google-maps-key' \ -l 'newlat, newlon' \ @@ -247,7 +246,7 @@ If you have a docker image for a notification webhook that you want to be called ``` docker run -d --name pogomap --net=pogonw -p 5000:5000 \ - pokemap/pokemongo-map \ + frostthefox/pokemongo-map \ -a ptc -u username -p password \ -k 'your-google-maps-key' \ -l 'lat, lon' \ diff --git a/docs/basic-install/index.rst b/docs/basic-install/index.rst index 9f0a2a5928..cac2195098 100644 --- a/docs/basic-install/index.rst +++ b/docs/basic-install/index.rst @@ -87,7 +87,7 @@ Linux/OSX: In order to run from a git clone, you must compile the front-end assets with node. Make sure you have node installed for your platform: - * `Windows `_ + * `Windows `_ * `OSX `_ * Linux -- refer to the `package installation `_ for your flavor of OS @@ -150,7 +150,7 @@ If you are running a ``git`` version, you can update with a few quick commands: .. code-block:: bash git pull - pip install -r requirements.txt --upgrade + pip install -r requirements.txt --upgrade (Prepend sudo -H on Linux) npm install npm run build diff --git a/docs/basic-install/linux.rst b/docs/basic-install/linux.rst index 012f12346c..a6d75b288a 100644 --- a/docs/basic-install/linux.rst +++ b/docs/basic-install/linux.rst @@ -10,8 +10,10 @@ You can install the required packages on Ubuntu by running the following command .. code-block:: bash + sudo apt-get install -y python python-pip python-dev build-essential git curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - - sudo apt-get install -y python python-pip python-dev nodejs nodejs-legacy build-essential + sudo apt-get install -y nodejs + Debian 7/8 ********** diff --git a/docs/extras/Community-Tools.md b/docs/extras/Community-Tools.md index a44648aaab..08b77a1907 100644 --- a/docs/extras/Community-Tools.md +++ b/docs/extras/Community-Tools.md @@ -1,10 +1,19 @@ # Community Tools Some useful tools made by the community for the community -## [PTC Account Generator](https://github.com/skvvv/pikapy) +## [ptc-acc-gen](https://github.com/FrostTheFox/ptc-acc-gen) +A PTC account generation script, generates any # of accounts. ToS verification/trainer name setting via PogoPlayer. Google Scripts script to accept email verification included. Outputs in .csv format. Semi-auto (Manually finish captcha) and automatic (Automatically finish captcha using 2captcha) modes. + +## [PTC Account Generator](https://github.com/sriyegna/Pikaptcha) ### An automation script that can create any number of Nintendo Pokémon Trainer Club accounts Used to generate any desired number of PTC accounts - TOS verifies them and includes a google script that can be used to verify all the emails. Outputs generated account information in .csv format. +## [PGM Multi Loc](https://beccasafan.github.io/pgm-multiloc/) +### Easily visualize locations on a map before scanning, and generate a customized launch script. +Add multiple scan locations on the map. Automatically convert an area to a beehive. Resize and move the location on the map. Disable individual hives to stop scanning a specific location. + +Generate a customized launch script, with the ability to edit the templates used for the individual commands. Pass in a list of account information that contains usernames, passwords, proxies, etc. + ## [Cor3Zer0's Map Calculator](https://github.com/Cor3Zer0/Map-Calculator) ### Calculator that helps in the creation of PokemonGo Map Used to calculate optimized flags for particular use cases given a set situation. diff --git a/docs/extras/commandline.md b/docs/extras/commandline.md new file mode 100644 index 0000000000..44c45192b2 --- /dev/null +++ b/docs/extras/commandline.md @@ -0,0 +1,210 @@ +# Command Line + + usage: runserver.py + [-h] [-cf CONFIG] [-a AUTH_SERVICE] [-u USERNAME] + [-p PASSWORD] [-w WORKERS] [-asi ACCOUNT_SEARCH_INTERVAL] + [-ari ACCOUNT_REST_INTERVAL] [-ac ACCOUNTCSV] + [-l LOCATION] [-j] [-st STEP_LIMIT] [-sd SCAN_DELAY] + [-ld LOGIN_DELAY] [-lr LOGIN_RETRIES] [-mf MAX_FAILURES] + [-msl MIN_SECONDS_LEFT] [-dc] [-H HOST] [-P PORT] + [-L LOCALE] [-c] [-m MOCK] [-ns] [-os] [-nsc] [-fl] -k + GMAPS_KEY [--skip-empty] [-C] [-D DB] [-cd] [-np] + [-ng] [-nk] [-ss [SPAWNPOINT_SCANNING]] + [--dump-spawnpoints] [-pd PURGE_DATA] [-px PROXY] + [-pxt PROXY_TIMEOUT] [-pxd PROXY_DISPLAY] + [--db-type DB_TYPE] [--db-name DB_NAME] + [--db-user DB_USER] [--db-pass DB_PASS] + [--db-host DB_HOST] [--db-port DB_PORT] + [--db-max_connections DB_MAX_CONNECTIONS] + [--db-threads DB_THREADS] [-wh [WEBHOOKS [WEBHOOKS ...]]] + [-gi] [--webhook-updates-only] [--wh-threads WH_THREADS] + [--ssl-certificate SSL_CERTIFICATE] + [--ssl-privatekey SSL_PRIVATEKEY] [-ps] [-sn STATUS_NAME] + [-spp STATUS_PAGE_PASSWORD] [-el ENCRYPT_LIB] + [-v [filename.log] | -vv [filename.log] | -d] + + Args that start with '--' (eg. -a) can also be set in a config file + (default: /config/config.ini or via -cf). + The recognized syntax for setting (key, value) pairs is based on the INI and + YAML formats (e.g. key=value or foo=TRUE). For full documentation of the + differences from the standards please refer to the ConfigArgParse + documentation. If an arg is specified in more than one place, then commandline + values override environment variables which override config file values which + override defaults. + + optional arguments: + -h, --help show this help message and exit [env var: + POGOMAP_HELP] + -cf CONFIG, --config CONFIG + Configuration file. See docs/extras/configuration-files.md + -a AUTH_SERVICE, --auth-service AUTH_SERVICE + Auth Services, either one for all accounts or one per + account: ptc or google. Defaults all to ptc. [env var: + POGOMAP_AUTH_SERVICE] + -u USERNAME, --username USERNAME + Usernames, one per account. [env var: + POGOMAP_USERNAME] + -p PASSWORD, --password PASSWORD + Passwords, either single one for all accounts or one + per account. [env var: POGOMAP_PASSWORD] + -w WORKERS, --workers WORKERS + Number of search worker threads to start. Defaults to + the number of accounts specified. [env var: + POGOMAP_WORKERS] + -asi ACCOUNT_SEARCH_INTERVAL, --account-search-interval ACCOUNT_SEARCH_INTERVAL + Seconds for accounts to search before switching to a + new account. 0 to disable. [env var: + POGOMAP_ACCOUNT_SEARCH_INTERVAL] + -ari ACCOUNT_REST_INTERVAL, --account-rest-interval ACCOUNT_REST_INTERVAL + Seconds for accounts to rest when they fail or are + switched out [env var: POGOMAP_ACCOUNT_REST_INTERVAL] + -ac ACCOUNTCSV, --accountcsv ACCOUNTCSV + Load accounts from CSV file containing + "auth_service,username,passwd" lines [env var: + POGOMAP_ACCOUNTCSV] + -l LOCATION, --location LOCATION + Location, can be an address or coordinates [env var: + POGOMAP_LOCATION] + -j, --jitter Apply random -9m to +9m jitter to location [env var: + POGOMAP_JITTER] + -st STEP_LIMIT, --step-limit STEP_LIMIT + Steps [env var: POGOMAP_STEP_LIMIT] + -sd SCAN_DELAY, --scan-delay SCAN_DELAY + Time delay between requests in scan threads [env var: + POGOMAP_SCAN_DELAY] + -ld LOGIN_DELAY, --login-delay LOGIN_DELAY + Time delay between each login attempt [env var: + POGOMAP_LOGIN_DELAY] + -lr LOGIN_RETRIES, --login-retries LOGIN_RETRIES + Number of logins attempts before refreshing a thread + [env var: POGOMAP_LOGIN_RETRIES] + -mf MAX_FAILURES, --max-failures MAX_FAILURES + Maximum number of failures to parse locations before + an account will go into a two hour sleep [env var: + POGOMAP_MAX_FAILURES] + -msl MIN_SECONDS_LEFT, --min-seconds-left MIN_SECONDS_LEFT + Time that must be left on a spawn before considering + it too late and skipping it. eg. 600 would skip + anything with < 10 minutes remaining. Default 0. [env + var: POGOMAP_MIN_SECONDS_LEFT] + -dc, --display-in-console + Display Found Pokemon in Console [env var: + POGOMAP_DISPLAY_IN_CONSOLE] + -H HOST, --host HOST Set web server listening host [env var: POGOMAP_HOST] + -P PORT, --port PORT Set web server listening port [env var: POGOMAP_PORT] + -L LOCALE, --locale LOCALE + Locale for Pokemon names (default: en, check + static/dist/locales for more) [env var: + POGOMAP_LOCALE] + -c, --china Coordinates transformer for China [env var: + POGOMAP_CHINA] + -m MOCK, --mock MOCK Mock mode - point to a fpgo endpoint instead of using + the real PogoApi, ec: http://127.0.0.1:9090 [env var: + POGOMAP_MOCK] + -ns, --no-server No-Server Mode. Starts the searcher but not the + Webserver. [env var: POGOMAP_NO_SERVER] + -os, --only-server Server-Only Mode. Starts only the Webserver without + the searcher. [env var: POGOMAP_ONLY_SERVER] + -nsc, --no-search-control + Disables search control [env var: + POGOMAP_NO_SEARCH_CONTROL] + -fl, --fixed-location + Hides the search bar for use in shared maps. [env var: + POGOMAP_FIXED_LOCATION] + -k GMAPS_KEY, --gmaps-key GMAPS_KEY + Google Maps Javascript API Key [env var: + POGOMAP_GMAPS_KEY] + --skip-empty Enables skipping of empty cells in normal scans - + requires previously populated database (not to be used + with -ss) [env var: POGOMAP_SKIP_EMPTY] + -C, --cors Enable CORS on web server [env var: POGOMAP_CORS] + -D DB, --db DB Database filename [env var: POGOMAP_DB] + -cd, --clear-db Deletes the existing database before starting the + Webserver. [env var: POGOMAP_CLEAR_DB] + -np, --no-pokemon Disables Pokemon from the map (including parsing them + into local db) [env var: POGOMAP_NO_POKEMON] + -ng, --no-gyms Disables Gyms from the map (including parsing them + into local db) [env var: POGOMAP_NO_GYMS] + -nk, --no-pokestops Disables PokeStops from the map (including parsing + them into local db) [env var: POGOMAP_NO_POKESTOPS] + -ss [SPAWNPOINT_SCANNING], --spawnpoint-scanning [SPAWNPOINT_SCANNING] + Use spawnpoint scanning (instead of hex grid). Scans + in a circle based on step_limit when on DB [env var: + POGOMAP_SPAWNPOINT_SCANNING] + --dump-spawnpoints dump the spawnpoints from the db to json (only for use + with -ss) [env var: POGOMAP_DUMP_SPAWNPOINTS] + -pd PURGE_DATA, --purge-data PURGE_DATA + Clear pokemon from database this many hours after they + disappear (0 to disable) [env var: POGOMAP_PURGE_DATA] + -px PROXY, --proxy PROXY + Proxy url (e.g. socks5://127.0.0.1:9050) [env var: + POGOMAP_PROXY] + -pxsc, --proxy-skip-check + Disable checking of proxies before start [env var: + POGOMAP_PROXY_SKIP_CHECK] + -pxt PROXY_TIMEOUT, --proxy-timeout PROXY_TIMEOUT + Timeout settings for proxy checker in seconds [env + var: POGOMAP_PROXY_TIMEOUT] + -pxd PROXY_DISPLAY, --proxy-display PROXY_DISPLAY + Display info on which proxy beeing used (index or + full) To be used with -ps [env var: + POGOMAP_PROXY_DISPLAY] + --db-type DB_TYPE Type of database to be used (default: sqlite) [env + var: POGOMAP_DB_TYPE] + --db-name DB_NAME Name of the database to be used [env var: + POGOMAP_DB_NAME] + --db-user DB_USER Username for the database [env var: POGOMAP_DB_USER] + --db-pass DB_PASS Password for the database [env var: POGOMAP_DB_PASS] + --db-host DB_HOST IP or hostname for the database [env var: + POGOMAP_DB_HOST] + --db-port DB_PORT Port for the database [env var: POGOMAP_DB_PORT] + --db-max_connections DB_MAX_CONNECTIONS + Max connections (per thread) for the database [env + var: POGOMAP_DB_MAX_CONNECTIONS] + --db-threads DB_THREADS + Number of db threads; increase if the db queue falls + behind [env var: POGOMAP_DB_THREADS] + -wh [WEBHOOKS [WEBHOOKS ...]], --webhook [WEBHOOKS [WEBHOOKS ...]] + Define URL(s) to POST webhook information to [env var: + POGOMAP_WEBHOOK] + -gi, --gym-info Get all details about gyms (causes an additional API + hit for every gym) [env var: POGOMAP_GYM_INFO] + --disable-clean Disable clean db loop [env var: POGOMAP_DISABLE_CLEAN] + --webhook-updates-only + Only send updates (pokémon & lured pokéstops) [env + var: POGOMAP_WEBHOOK_UPDATES_ONLY] + --wh-threads WH_THREADS + Number of webhook threads; increase if the webhook + queue falls behind [env var: POGOMAP_WH_THREADS] + --ssl-certificate SSL_CERTIFICATE + Path to SSL certificate file [env var: + POGOMAP_SSL_CERTIFICATE] + --ssl-privatekey SSL_PRIVATEKEY + Path to SSL private key file [env var: + POGOMAP_SSL_PRIVATEKEY] + -ps, --print-status Show a status screen instead of log messages. Can + switch between status and logs by pressing enter. [env + var: POGOMAP_PRINT_STATUS] + -sn STATUS_NAME, --status-name STATUS_NAME + Enable status page database update using STATUS_NAME + as main worker name [env var: POGOMAP_STATUS_NAME] + -spp STATUS_PAGE_PASSWORD, --status-page-password STATUS_PAGE_PASSWORD + Set the status page password [env var: + POGOMAP_STATUS_PAGE_PASSWORD] + -el ENCRYPT_LIB, --encrypt-lib ENCRYPT_LIB + Path to encrypt lib to be used instead of the shipped + ones [env var: POGOMAP_ENCRYPT_LIB] + -odt ON_DEMAND_TIMEOUT, --on-demand_timeout ON_DEMAND_TIMEOUT + Pause searching while web UI is inactive for this + timeout(in seconds) [env var: + POGOMAP_ON_DEMAND_TIMEOUT] + -v [filename.log], --verbose [filename.log] + Show debug messages from PomemonGo-Map and pgoapi. + Optionally specify file to log to. [env var: + POGOMAP_VERBOSE] + -vv [filename.log], --very-verbose [filename.log] + Like verbose, but show debug messages from all modules + as well. Optionally specify file to log to. [env var: + POGOMAP_VERY_VERBOSE] + -d, --debug Deprecated, use -v or -vv instead. [env var: + POGOMAP_DEBUG] diff --git a/docs/extras/configuration-files.md b/docs/extras/configuration-files.md new file mode 100644 index 0000000000..7fa379def6 --- /dev/null +++ b/docs/extras/configuration-files.md @@ -0,0 +1,51 @@ +# Configuration files + +Configuration files can be used to organize server/scanner deployments. Any long-form command-line argument can be specified in a configuration file. + +## Default file + +The default configuration file is *config/config.ini* underneath the project home. However, this location can be changed by setting the environment variable POGOMAP_CONFIG or using the -cf or --config flag on the command line. In the event that both the environment variable and the command line argument exists, the command line value will take precedence. Note that all relative pathnames are relative to the current working directory (often, but not necessarily where runserver.py is located). + +## Setting configuration key/value pairs + + For command line values that take a single value they can be specified as: + + keyname: value + e.g. host: 0.0.0.0 + + For parameters that may be repeated: + + keyname: [ value1, value2, ...] + e.g. username: [ randomjoe, bonnieclyde ] + + For command line arguments that take no parameters: + + keyname: True + e.g. fixed-location: True + +## Example config file + +
+  -- contents of file myconfig.seattle --
+  username: [ randomjoe, bob ]
+  password: [ password1, password2 ]
+  location: seattle, wa
+  step-limit: 5
+  gmaps-key: MyGmapsKeyGoesHereSomeLongString
+  print-status: True
+  -- end of file --
+  
+ + Running this config file as: + + python runserver.py -cf myconfig.seattle + + would be the same as running with the following command line: + + python runserver.py -u randomjoe -p password1 -u bob -p password2 -l "seattle, wa" -st 5 -k MyGmapsKeyGoesHereSomeLongString -ps + +## Running multiple configs + + One common way of running multiple locations is to use two configuration files each with common or default database values, but with different location specs. The first configuration running as both a scanner and a server, and in the second configuration file, use the *no-server* flag to not start the web interface for the second configuration. In the config file, this would mean including a line like: + + no-server: True diff --git a/docs/extras/faq.md b/docs/extras/faq.md index 34d0784aec..c6a9ca7ee5 100644 --- a/docs/extras/faq.md +++ b/docs/extras/faq.md @@ -15,7 +15,7 @@ The -dp, -dg -dl, -i, -o and -ar parameters are no longer needed. Remove them fr [See this helpful guide](external.md) -## "It's acting like the location flag is missing. +## "It's acting like the location flag is missing." `-l`, never forget. @@ -47,3 +47,25 @@ error: command 'gcc' failed with exit status 1 Your OS is missing the `gcc` compiler library. For Debian, run `apt-get install build-essentials`. For Red Hat, run `yum groupinstall 'Development Tools'` +## Formulas? +``` +st=step distance + +sd=scan delay [default: 10] + +w=# of workers + +t=desired scan time +``` +time to scan: +`(sd/w)*(3st^2-3st+1)` + +workers needed: +`(sd/t)*(3st^2-3st+1)` + +time to scan (using default scan delay): +`(10/w)*(3st^2-3st+1)` + +workers needed (using default scan delay): +`(10/t)*(3st^2-3st+1)` + diff --git a/docs/extras/multi-account.md b/docs/extras/multi-account.md index c1b05d9521..91d87c4e07 100644 --- a/docs/extras/multi-account.md +++ b/docs/extras/multi-account.md @@ -7,12 +7,12 @@ PokemonGo-Map supports using multiple accounts to run a worker with multiple thr To use multiple accounts when running from the command line, you must specify multiple -u and -p values. -Example: `python runserver.py -u thunderfox01 -u thunderfox02 -p thunderfox01 -p thunderfox02` +Example: `python runserver.py -a ptc -u thunderfox01 -u thunderfox02 -p thunderfox01 -p thunderfox02` If you have multiple accounts with the same password, you can specify one -p value. PokemonGo-Map will use the value for all specified accounts. -Example: `python runserver.py -u thunderfox01 -u thunderfox02 -p thunderfox` +Example: `python runserver.py -a ptc -u thunderfox01 -u thunderfox02 -p thunderfox` If you have multiple accounts with different auth services, you can specify multiple -a values. @@ -25,6 +25,7 @@ To use multiple accounts with config.ini, you must surround all the accounts and Example: ``` +auth-service: ptc username: [thunderfox01, thunderfox02] password: [password01, password02] ``` @@ -34,6 +35,7 @@ If you have multiple accounts with the same password, you can specify one passwo Example: ``` +auth-service: ptc username: [thunderfox01, thunderfox02] password: password ``` @@ -49,12 +51,12 @@ password: [password01, password02, password03] ## Using CSV file: -To use multiple accounts from a CSV file, you create a CSV file with the auth method, username and password on each line. Additional fields after the password are ignored. +To use multiple accounts from a CSV file, you create a CSV file with the auth method, username and password on each line. Shorten to 3 fields only, more than 3 fields will error out. CSV File Example: ``` ptc,thunderfox01,password01 -ptc,thunderfox02,password02,other,information +google,thunderfox02@gmail.com,password02 ``` Example: `python runserver.py -ac accounts.csv` diff --git a/docs/extras/nginx.md b/docs/extras/nginx.md index 6a5b48b818..e21c99e114 100644 --- a/docs/extras/nginx.md +++ b/docs/extras/nginx.md @@ -71,3 +71,94 @@ server { } } ``` + +## Adding simple httpd Authentication. + +This will guide you through setting up simple HTTP Authentication using nginx and reverse proxy protocols. These instructions are written for someone using a Debian/Ubuntu VPS. Your enviroment may have slightly different requirements, however the concepts as a whole should still stand. This guide assumes you have nginx installed and running, and a `conf.d/*.conf` file created, such as `/etc/nginx/conf.d/pokemongo-map.conf`, as the example above provides, and that you're running your service on port 5000, and want it to be accessable at http://your_ip/go/, although it supports other ports and locations. + +`*` denotes a wildcard, and will be used to stand for your site's `*.conf` file, please __do not__ literally type `sudo nano /etc/nginx/conf.d/*.conf`. + +1. Create a .htpasswd file inside `/etc/nginx/`. Some suggested methods to create a .htpasswd file are below. + - Linux users can use the apache2-tools package to create the files. + -First, get the apache2-utils package + ``` + sudo apt-get install apache2-utils + ``` + -Then run the htpasswd command + ``` + sudo htpasswd -c /etc/nginx/.htpasswd exampleuser + ``` + + This will prompt you for a new password for user exampleuser. Remove the `-c` tag for additional entries to the file. Opening the file with a text exitor such as nano should show one line for each user, with an encrypted password following, in the format of user:pass. + + - Manual generation of the file can be done using tools such as: http://www.htaccesstools.com/htpasswd-generator/. After manually generating the file, please place it in `/etc/nginx/`, or wherever your distro installs `nginx.conf` and the rest of your config files. +2. Open your `*.conf` file with a text editor, with a command such as `sudo nano /etc/nginx/conf.d/pokemongo-map.conf`. Add the following two lines underneath the domain path. + + ``` + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/.htpasswd; + ``` + + If your `*.conf` file matches the example provided above, you should have the following. + + ``` + server { + location /go/ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/.htpasswd; + proxy_pass http://127.0.0.1:5000/; + } + } + ``` + Now, we're going to go ahead and fill out the `*.conf` file with the rest of the information to make our service work, and shore up our nginx config, by appending the following between the authentication block and proxy_pass. + + ``` + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto http; + proxy_set_header Host $http_host; + proxy_redirect off; + ``` + + Here is a fully completed example `*.conf`, with working httpd authentication. Notice, this example does not use SSL / 443, although the method can be adapted to it! + + ``` + upstream pokemonmap{ + server 127.0.0.1:5000 fail_timeout=0 + } + server { + listen 80; + server_name [sub.domain.com] [your_ip]; + + location /go/ { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/.htpasswd; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto http; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://[your_ip]:5000; + break; + } + } + ``` +3. Test your nginx configuration using the command `sudo nginx -t`. If this returns a positive result, restart the nginx service using `sudo service nginx restart`. +4. Verify your configuration is working by loading http://your_ip/go/ or http://sub.your.domain/go/, or however else you have it set in your `*.conf`. Please verify it's working before proceeding to step 5, or it will be much harder to troubleshoot! + + Troubleshooting: + - **I can't reach my server at http://your_ip/go/!** + + Check http://your_ip:5000/. If you cannot see it there, your issue lies with your server, not with nginx! If you can see it there, but cannot see it at http://your_ip/go/, your issue lies with nginx. Please check your config files to make sure they are pointing at the right places, and that your `sudo nginx -t` checks out. + + - **nginx -t doesn't check out.** + + Check the error messages for which line is creating the error, and work your way backwards from there. Many times it's just a missed `;` or `}`. + +5. Finally, we're going to modify our runserver.py command to operate with the `-H 127.0.0.1` flag, only allowing our webapp to be accessable from Localhost. As nginx is running on the local system, nginx will still be able to fetch the webapp, and serve it, through the proxy and authentication, to remote users, but remote users will not be able to connect directly to the webapp itself. If your runserver command is + + ```python runserver.py -u user -p pass -k key -l "my,coords" -st 10 -P 5000``` + + You are going to want to update it to the following: + + ```python runserver.py -u user -p pass -k key -l "my,coords" -st 10 -P 5000 -H 127.0.0.1``` + + From there, we're going to want to check and see that you can get to your server, albeit through authentication, at http://your_ip/go/, and that you cannot get to your server at http://your_ip/go:5000/. If that works, you're all set up! diff --git a/pogom/app.py b/pogom/app.py index 6ac0f3e9a9..f9bdd69023 100755 --- a/pogom/app.py +++ b/pogom/app.py @@ -15,7 +15,7 @@ from . import config from .models import Pokemon, Gym, Pokestop, ScannedLocation, MainWorker, WorkerStatus - +from .utils import now log = logging.getLogger(__name__) compress = Compress() @@ -39,6 +39,9 @@ def __init__(self, import_name, **kwargs): def set_search_control(self, control): self.search_control = control + def set_heartbeat_control(self, heartb): + self.heartbeat = heartb + def set_location_queue(self, queue): self.location_queue = queue @@ -50,7 +53,7 @@ def get_search_control(self): def post_search_control(self): args = get_args() - if not args.search_control: + if not args.search_control or args.on_demand_timeout > 0: return 'Search control is disabled', 403 action = request.args.get('action', 'none') if action == 'on': @@ -64,9 +67,12 @@ def post_search_control(self): return self.get_search_control() def fullmap(self): + self.heartbeat[0] = now() args = get_args() + if args.on_demand_timeout > 0: + self.search_control.clear() fixed_display = "none" if args.fixed_location else "inline" - search_display = "inline" if args.search_control else "none" + search_display = "inline" if args.search_control and args.on_demand_timeout <= 0 else "none" return render_template('map.html', lat=self.current_location[0], @@ -78,6 +84,10 @@ def fullmap(self): ) def raw_data(self): + self.heartbeat[0] = now() + args = get_args() + if args.on_demand_timeout > 0: + self.search_control.clear() d = {} swLat = request.args.get('swLat') swLng = request.args.get('swLng') diff --git a/pogom/models.py b/pogom/models.py index 7e123d6466..fda01b75fd 100755 --- a/pogom/models.py +++ b/pogom/models.py @@ -30,7 +30,7 @@ flaskDb = FlaskDB() cache = TTLCache(maxsize=100, ttl=60 * 5) -db_schema_version = 7 +db_schema_version = 8 class MyRetryDB(RetryOperationalError, PooledMySQLDatabase): @@ -83,6 +83,11 @@ class Pokemon(BaseModel): latitude = DoubleField() longitude = DoubleField() disappear_time = DateTimeField(index=True) + individual_attack = IntegerField(null=True) + individual_defense = IntegerField(null=True) + individual_stamina = IntegerField(null=True) + move_1 = IntegerField(null=True) + move_2 = IntegerField(null=True) class Meta: indexes = ((('latitude', 'longitude'), False),) @@ -573,16 +578,66 @@ def hex_bounds(center, steps): return (n, e, s, w) +def construct_pokemon_dict(pokemons, p, encounter_result, d_t): + pokemons[p['encounter_id']] = { + 'encounter_id': b64encode(str(p['encounter_id'])), + 'spawnpoint_id': p['spawn_point_id'], + 'pokemon_id': p['pokemon_data']['pokemon_id'], + 'latitude': p['latitude'], + 'longitude': p['longitude'], + 'disappear_time': d_t, + } + if encounter_result is not None and 'wild_pokemon' in encounter_result['responses']['ENCOUNTER']: + pokemon_info = encounter_result['responses']['ENCOUNTER']['wild_pokemon']['pokemon_data'] + attack = pokemon_info.get('individual_attack', 0) + defense = pokemon_info.get('individual_defense', 0) + stamina = pokemon_info.get('individual_stamina', 0) + pokemons[p['encounter_id']].update({ + 'individual_attack': attack, + 'individual_defense': defense, + 'individual_stamina': stamina, + 'move_1': pokemon_info['move_1'], + 'move_2': pokemon_info['move_2'], + }) + else: + if encounter_result is not None and 'wild_pokemon' not in encounter_result['responses']['ENCOUNTER']: + log.warning("Error encountering {}, status code: {}".format(p['encounter_id'], encounter_result['responses']['ENCOUNTER']['status'])) + pokemons[p['encounter_id']].update({ + 'individual_attack': None, + 'individual_defense': None, + 'individual_stamina': None, + 'move_1': None, + 'move_2': None, + }) + + # todo: this probably shouldn't _really_ be in "models" anymore, but w/e -def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue): +def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue, api): pokemons = {} pokestops = {} gyms = {} + skipped = 0 + encountered_pokemon = [] cells = map_dict['responses']['GET_MAP_OBJECTS']['map_cells'] for cell in cells: if config['parse_pokemon']: + # pre-build a list of encountered pokemon + encounter_ids = [b64encode(str(p['encounter_id'])) for p in cell.get('wild_pokemons', [])] + if encounter_ids: + query = (Pokemon + .select() + .where((Pokemon.disappear_time > datetime.utcnow()) & (Pokemon.encounter_id << encounter_ids)) + .dicts() + ) + encountered_pokemon = [(p['encounter_id'], p['spawnpoint_id']) for p in query] + for p in cell.get('wild_pokemons', []): + # Don't parse pokemon we've already encountered. Avoids IVs getting nulled out on rescanning. + if (b64encode(str(p['encounter_id'])), p['spawn_point_id']) in encountered_pokemon: + skipped += 1 + continue + # time_till_hidden_ms was overflowing causing a negative integer. # It was also returning a value above 3.6M ms. if 0 < p['time_till_hidden_ms'] < 3600000: @@ -595,14 +650,17 @@ def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue): printPokemon(p['pokemon_data']['pokemon_id'], p['latitude'], p['longitude'], d_t) - pokemons[p['encounter_id']] = { - 'encounter_id': b64encode(str(p['encounter_id'])), - 'spawnpoint_id': p['spawn_point_id'], - 'pokemon_id': p['pokemon_data']['pokemon_id'], - 'latitude': p['latitude'], - 'longitude': p['longitude'], - 'disappear_time': d_t - } + + # Scan for IVs and moves + encounter_result = None + if (args.encounter and (p['pokemon_data']['pokemon_id'] in args.encounter_whitelist or + p['pokemon_data']['pokemon_id'] not in args.encounter_blacklist and not args.encounter_whitelist)): + time.sleep(args.encounter_delay) + encounter_result = api.encounter(encounter_id=p['encounter_id'], + spawn_point_id=p['spawn_point_id'], + player_latitude=step_location[0], + player_longitude=step_location[1]) + construct_pokemon_dict(pokemons, p, encounter_result, d_t) if args.webhooks: wh_update_queue.put(('pokemon', { @@ -613,7 +671,12 @@ def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue): 'longitude': p['longitude'], 'disappear_time': calendar.timegm(d_t.timetuple()), 'last_modified_time': p['last_modified_timestamp_ms'], - 'time_until_hidden_ms': p['time_till_hidden_ms'] + 'time_until_hidden_ms': p['time_till_hidden_ms'], + 'individual_attack': pokemons[p['encounter_id']]['individual_attack'], + 'individual_defense': pokemons[p['encounter_id']]['individual_defense'], + 'individual_stamina': pokemons[p['encounter_id']]['individual_stamina'], + 'move_1': pokemons[p['encounter_id']]['move_1'], + 'move_2': pokemons[p['encounter_id']]['move_2'] })) for f in cell.get('forts', []): @@ -701,7 +764,7 @@ def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue): db_update_queue.put((Gym, gyms)) log.info('Parsing found %d pokemons, %d pokestops, and %d gyms', - len(pokemons), + len(pokemons) + skipped, len(pokestops), len(gyms)) @@ -712,7 +775,7 @@ def parse_map(args, map_dict, step_location, db_update_queue, wh_update_queue): }})) return { - 'count': len(pokemons) + len(pokestops) + len(gyms), + 'count': len(pokemons) + skipped + len(pokestops) + len(gyms), 'gyms': gyms, } @@ -898,6 +961,7 @@ def clean_db_loop(args): .delete() .where((Pokemon.disappear_time < (datetime.utcnow() - timedelta(hours=args.purge_data))))) + query.execute() log.info('Regular database cleaning complete') time.sleep(60) @@ -908,7 +972,13 @@ def clean_db_loop(args): def bulk_upsert(cls, data): num_rows = len(data.values()) i = 0 - step = 120 + + if args.db_type == 'mysql': + step = 120 + else: + # SQLite has a default max number of parameters of 999, + # so we need to limit how many rows we insert for it. + step = 50 while i < num_rows: log.debug('Inserting items %d to %d', i, min(i + step, num_rows)) @@ -1009,3 +1079,12 @@ def database_migrate(db, old_ver): migrator.drop_column('gymdetails', 'description'), migrator.add_column('gymdetails', 'description', TextField(null=True, default="")) ) + + if old_ver < 8: + migrate( + migrator.add_column('pokemon', 'individual_attack', IntegerField(null=True, default=0)), + migrator.add_column('pokemon', 'individual_defense', IntegerField(null=True, default=0)), + migrator.add_column('pokemon', 'individual_stamina', IntegerField(null=True, default=0)), + migrator.add_column('pokemon', 'move_1', IntegerField(null=True, default=0)), + migrator.add_column('pokemon', 'move_2', IntegerField(null=True, default=0)) + ) diff --git a/pogom/search.py b/pogom/search.py index a05e8a347c..2394d203a9 100644 --- a/pogom/search.py +++ b/pogom/search.py @@ -251,7 +251,7 @@ def worker_status_db_thread(threads_status, name, db_updates_queue): # The main search loop that keeps an eye on the over all process -def search_overseer_thread(args, new_location_queue, pause_bit, encryption_lib_path, db_updates_queue, wh_queue): +def search_overseer_thread(args, new_location_queue, pause_bit, heartb, encryption_lib_path, db_updates_queue, wh_queue): log.info('Search overseer starting') @@ -345,6 +345,10 @@ def search_overseer_thread(args, new_location_queue, pause_bit, encryption_lib_p # The real work starts here but will halt on pause_bit.set() while True: + if args.on_demand_timeout > 0 and (now() - args.on_demand_timeout) > heartb[0]: + pause_bit.set() + log.info("Searching paused due to inactivity...") + # Wait here while scanning is paused while pause_bit.is_set(): scheduler.scanning_paused() @@ -433,6 +437,10 @@ def search_worker_thread(args, account_queue, account_failures, search_items_que account_failures.append({'account': account, 'last_fail_time': now(), 'reason': 'failures'}) break # exit this loop to get a new account and have the API recreated + while pause_bit.is_set(): + status['message'] = 'Scanning paused' + time.sleep(2) + # If this account has been running too long, let it rest if (args.account_search_interval is not None): if (status['starttime'] <= (now() - args.account_search_interval)): @@ -441,10 +449,6 @@ def search_worker_thread(args, account_queue, account_failures, search_items_que account_failures.append({'account': account, 'last_fail_time': now(), 'reason': 'rest interval'}) break - while pause_bit.is_set(): - status['message'] = 'Scanning paused' - time.sleep(2) - # Grab the next thing to search (when available) status['message'] = 'Waiting for item from queue' step, step_location, appears, leaves = search_items_queue.get() @@ -477,15 +481,18 @@ def search_worker_thread(args, account_queue, account_failures, search_items_que # No sleep here; we've not done anything worth sleeping for. Plus we clearly need to catch up! continue - status['message'] = 'Searching at {:6f},{:6f}'.format(step_location[0], step_location[1]) - log.info(status['message']) - # Let the api know where we intend to be for this loop + # doing this before check_login so it does not also have to be done there + # when the auth token is refreshed api.set_position(*step_location) # Ok, let's get started -- check our login status check_login(args, account, api, step_location, status['proxy_url']) + # putting this message after the check_login so the messages aren't out of order + status['message'] = 'Searching at {:6f},{:6f}'.format(step_location[0], step_location[1]) + log.info(status['message']) + # Make the actual request (finally!) response_dict = map_request(api, step_location, args.jitter) @@ -500,7 +507,7 @@ def search_worker_thread(args, account_queue, account_failures, search_items_que # Got the response, parse it out, send todo's to db/wh queues try: - parsed = parse_map(args, response_dict, step_location, dbq, whq) + parsed = parse_map(args, response_dict, step_location, dbq, whq, api) search_items_queue.task_done() status[('success' if parsed['count'] > 0 else 'noitems')] += 1 consecutive_fails = 0 @@ -591,7 +598,6 @@ def check_login(args, account, api, position, proxy_url): # Try to login (a few times, but don't get stuck here) i = 0 - api.set_position(position[0], position[1], position[2]) while i < args.login_retries: try: if proxy_url: @@ -608,7 +614,7 @@ def check_login(args, account, api, position, proxy_url): time.sleep(args.login_delay) log.debug('Login for account %s successful', account['username']) - time.sleep(args.scan_delay) + time.sleep(20) def map_request(api, position, jitter=False): diff --git a/pogom/utils.py b/pogom/utils.py index 3a8caccffa..cc27a156b2 100755 --- a/pogom/utils.py +++ b/pogom/utils.py @@ -44,8 +44,9 @@ def wrapper(*args): @memoize def get_args(): # fuck PEP8 - configpath = os.path.join(os.path.dirname(__file__), '../config/config.ini') - parser = configargparse.ArgParser(default_config_files=[configpath], auto_env_var_prefix='POGOMAP_') + defaultconfigpath = os.getenv('POGOMAP_CONFIG', os.path.join(os.path.dirname(__file__), '../config/config.ini')) + parser = configargparse.ArgParser(default_config_files=[defaultconfigpath], auto_env_var_prefix='POGOMAP_') + parser.add_argument('-cf', '--config', is_config_file=True, help='Configuration file') parser.add_argument('-a', '--auth-service', type=str.lower, action='append', default=[], help='Auth Services, either one for all accounts or one per account: ptc or google. Defaults all to ptc.') parser.add_argument('-u', '--username', action='append', default=[], @@ -69,6 +70,17 @@ def get_args(): parser.add_argument('-sd', '--scan-delay', help='Time delay between requests in scan threads', type=float, default=10) + parser.add_argument('-enc', '--encounter', + help='Start an encounter to gather IVs and moves', + action='store_true', default=False) + parser.add_argument('-ed', '--encounter-delay', + help='Time delay between encounter pokemon in scan threads', + type=float, default=1) + encounter_list = parser.add_mutually_exclusive_group() + encounter_list.add_argument('-ewht', '--encounter-whitelist', action='append', default=[], + help='List of pokemon to encounter for more stats') + encounter_list.add_argument('-eblk', '--encounter-blacklist', action='append', default=[], + help='List of pokemon to NOT encounter for more stats') parser.add_argument('-ld', '--login-delay', help='Time delay between each login attempt', type=float, default=5) @@ -113,7 +125,7 @@ def get_args(): parser.add_argument('-k', '--gmaps-key', help='Google Maps Javascript API Key', required=True) - parser.add_argument('--spawnpoints-only', help='Only scan locations with spawnpoints in them.', + parser.add_argument('--skip-empty', help='Enables skipping of empty cells in normal scans - requires previously populated database (not to be used with -ss)', action='store_true', default=False) parser.add_argument('-C', '--cors', help='Enable CORS on web server', action='store_true', default=False) @@ -172,6 +184,7 @@ def get_args(): parser.add_argument('-spp', '--status-page-password', default=None, help='Set the status page password') parser.add_argument('-el', '--encrypt-lib', help='Path to encrypt lib to be used instead of the shipped ones') + parser.add_argument('-odt', '--on-demand_timeout', help='Pause searching while web UI is inactive for this timeout(in seconds)', type=int, default=0) verbosity = parser.add_mutually_exclusive_group() verbosity.add_argument('-v', '--verbose', help='Show debug messages from PomemonGo-Map and pgoapi. Optionally specify file to log to.', nargs='?', const='nofile', default=False, metavar='filename.log') verbosity.add_argument('-vv', '--very-verbose', help='Like verbose, but show debug messages from all modules as well. Optionally specify file to log to.', nargs='?', const='nofile', default=False, metavar='filename.log') @@ -262,6 +275,10 @@ def get_args(): else: field_error = 'password' + if num_fields > 3: + print 'Too many fields in accounts file: max supported are 3 fields. Found {} fields'.format(num_fields) + sys.exit(1) + # If something is wrong display error. if field_error != '': type_error = 'empty!' @@ -335,10 +352,13 @@ def get_args(): print(sys.argv[0] + ": Error: no accounts specified. Use -a, -u, and -p or --accountcsv to add accounts") sys.exit(1) + args.encounter_blacklist = [int(i) for i in args.encounter_blacklist] + args.encounter_whitelist = [int(i) for i in args.encounter_whitelist] + # Decide which scanning mode to use if args.spawnpoint_scanning: args.scheduler = 'SpawnScan' - elif args.spawnpoints_only: + elif args.skip_empty: args.scheduler = 'HexSearchSpawnpoint' else: args.scheduler = 'HexSearch' diff --git a/runserver.py b/runserver.py index 8692f9c736..523bb1f075 100755 --- a/runserver.py +++ b/runserver.py @@ -20,7 +20,7 @@ from pogom import config from pogom.app import Pogom -from pogom.utils import get_args, get_encryption_lib_path +from pogom.utils import get_args, get_encryption_lib_path, now from pogom.search import search_overseer_thread from pogom.models import init_database, create_tables, drop_tables, Pokemon, db_updater, clean_db_loop @@ -184,6 +184,8 @@ def main(): log.info('Parsing of Pokestops disabled') if args.no_gyms: log.info('Parsing of Gyms disabled') + if args.encounter: + log.info('Encountering pokemon enabled') config['LOCALE'] = args.locale config['CHINA'] = args.china @@ -203,6 +205,10 @@ def main(): # Control the search status (running or not) across threads pause_bit = Event() pause_bit.clear() + if args.on_demand_timeout > 0: + pause_bit.set() + + heartbeat = [now()] # Setup the location tracking queue and push the first location on new_location_queue = Queue() @@ -252,7 +258,7 @@ def main(): file.write(json.dumps(spawns)) log.info('Finished exporting spawn points') - argset = (args, new_location_queue, pause_bit, encryption_lib_path, db_updates_queue, wh_updates_queue) + argset = (args, new_location_queue, pause_bit, heartbeat, encryption_lib_path, db_updates_queue, wh_updates_queue) log.debug('Starting a %s search thread', args.scheduler) search_thread = Thread(target=search_overseer_thread, name='search-overseer', args=argset) @@ -266,6 +272,7 @@ def main(): init_cache_busting(app) app.set_search_control(pause_bit) + app.set_heartbeat_control(heartbeat) app.set_location_queue(new_location_queue) config['ROOT_PATH'] = app.root_path diff --git a/static/data/moves.json b/static/data/moves.json new file mode 100644 index 0000000000..156e582a27 --- /dev/null +++ b/static/data/moves.json @@ -0,0 +1,1058 @@ +{ + "222": { + "name": "Pound", + "type": "Normal", + "damage": 7, + "duration": 540, + "energy": 7, + "dps": 12.96 + }, + "228": { + "name": "Metal Claw", + "type": "Steel", + "damage": 8, + "duration": 630, + "energy": 7, + "dps": 12.69 + }, + "226": { + "name": "Psycho Cut", + "type": "Psychic", + "damage": 7, + "duration": 570, + "energy": 7, + "dps": 12.28 + }, + "210": { + "name": "Wing Attack", + "type": "Flying", + "damage": 9, + "duration": 750, + "energy": 7, + "dps": 12.0 + }, + "202": { + "name": "Bite", + "type": "Dark", + "damage": 6, + "duration": 500, + "energy": 7, + "dps": 12.0 + }, + "204": { + "name": "Dragon Breath", + "type": "Dragon", + "damage": 6, + "duration": 500, + "energy": 7, + "dps": 12.0 + }, + "220": { + "name": "Scratch", + "type": "Normal", + "damage": 6, + "duration": 500, + "energy": 7, + "dps": 12.0 + }, + "230": { + "name": "Water Gun", + "type": "Water", + "damage": 6, + "duration": 500, + "energy": 7, + "dps": 12.0 + }, + "240": { + "name": "Fire Fang", + "type": "Fire", + "damage": 10, + "duration": 840, + "energy": 4, + "dps": 11.9 + }, + "213": { + "name": "Shadow Claw", + "type": "Ghost", + "damage": 11, + "duration": 950, + "energy": 7, + "dps": 11.57 + }, + "238": { + "name": "Feint Attack", + "type": "Dark", + "damage": 12, + "duration": 1040, + "energy": 7, + "dps": 11.53 + }, + "224": { + "name": "Poison Jab", + "type": "Poison", + "damage": 12, + "duration": 1050, + "energy": 7, + "dps": 11.42 + }, + "234": { + "name": "Zen Headbutt", + "type": "Psychic", + "damage": 12, + "duration": 1050, + "energy": 4, + "dps": 11.42 + }, + "239": { + "name": "Steel Wing", + "type": "Steel", + "damage": 15, + "duration": 1330, + "energy": 4, + "dps": 11.27 + }, + "201": { + "name": "Bug Bite", + "type": "Bug", + "damage": 5, + "duration": 450, + "energy": 7, + "dps": 11.11 + }, + "218": { + "name": "Frost Breath", + "type": "Ice", + "damage": 9, + "duration": 810, + "energy": 7, + "dps": 11.11 + }, + "233": { + "name": "Mud Slap", + "type": "Ground", + "damage": 15, + "duration": 1350, + "energy": 9, + "dps": 11.11 + }, + "216": { + "name": "Mud Shot", + "type": "Ground", + "damage": 6, + "duration": 550, + "energy": 7, + "dps": 10.9 + }, + "221": { + "name": "Tackle", + "type": "Normal", + "damage": 12, + "duration": 1100, + "energy": 7, + "dps": 10.9 + }, + "237": { + "name": "Bubble", + "type": "Water", + "damage": 25, + "duration": 2300, + "energy": 15, + "dps": 10.86 + }, + "214": { + "name": "Vine Whip", + "type": "Grass", + "damage": 7, + "duration": 650, + "energy": 7, + "dps": 10.76 + }, + "217": { + "name": "Ice Shard", + "type": "Ice", + "damage": 15, + "duration": 1400, + "energy": 7, + "dps": 10.71 + }, + "241": { + "name": "Rock Smash", + "type": "Fighting", + "damage": 15, + "duration": 1410, + "energy": 7, + "dps": 10.63 + }, + "223": { + "name": "Cut", + "type": "Normal", + "damage": 12, + "duration": 1130, + "energy": 7, + "dps": 10.61 + }, + "236": { + "name": "Poison Sting", + "type": "Poison", + "damage": 6, + "duration": 575, + "energy": 4, + "dps": 10.43 + }, + "215": { + "name": "Razor Leaf", + "type": "Grass", + "damage": 15, + "duration": 1450, + "energy": 7, + "dps": 10.34 + }, + "212": { + "name": "Lick", + "type": "Ghost", + "damage": 5, + "duration": 500, + "energy": 7, + "dps": 10.0 + }, + "206": { + "name": "Spark", + "type": "Electric", + "damage": 7, + "duration": 700, + "energy": 4, + "dps": 10.0 + }, + "203": { + "name": "Sucker Punch", + "type": "Dark", + "damage": 7, + "duration": 700, + "energy": 4, + "dps": 10.0 + }, + "235": { + "name": "Confusion", + "type": "Psychic", + "damage": 15, + "duration": 1510, + "energy": 7, + "dps": 9.93 + }, + "225": { + "name": "Acid", + "type": "Poison", + "damage": 10, + "duration": 1050, + "energy": 7, + "dps": 9.52 + }, + "209": { + "name": "Ember", + "type": "Fire", + "damage": 10, + "duration": 1050, + "energy": 7, + "dps": 9.52 + }, + "227": { + "name": "Rock Throw", + "type": "Rock", + "damage": 12, + "duration": 1360, + "energy": 7, + "dps": 8.82 + }, + "211": { + "name": "Peck", + "type": "Flying", + "damage": 10, + "duration": 1150, + "energy": 10, + "dps": 8.69 + }, + "207": { + "name": "Low Kick", + "type": "Fighting", + "damage": 5, + "duration": 600, + "energy": 7, + "dps": 8.33 + }, + "205": { + "name": "Thunder Shock", + "type": "Electric", + "damage": 5, + "duration": 600, + "energy": 7, + "dps": 8.33 + }, + "229": { + "name": "Bullet Punch", + "type": "Steel", + "damage": 10, + "duration": 1200, + "energy": 7, + "dps": 8.33 + }, + "219": { + "name": "Quick Attack", + "type": "Normal", + "damage": 10, + "duration": 1330, + "energy": 7, + "dps": 7.51 + }, + "200": { + "name": "Fury Cutter", + "type": "Bug", + "damage": 3, + "duration": 400, + "energy": 12, + "dps": 7.5 + }, + "208": { + "name": "Karate Chop", + "type": "Fighting", + "damage": 6, + "duration": 800, + "energy": 7, + "dps": 7.5 + }, + "231": { + "name": "Splash", + "type": "Water", + "damage": 0, + "duration": 1230, + "energy": 7, + "dps": 0.0 + }, + "32": { + "name": "Stone Edge", + "type": "Rock", + "damage": 80, + "duration": 3100, + "energy": 100, + "dps": 25.8 + }, + "28": { + "name": "Cross Chop", + "type": "Fighting", + "damage": 60, + "duration": 2000, + "energy": 100, + "dps": 30.0 + }, + "83": { + "name": "Dragon Claw", + "type": "Dragon", + "damage": 35, + "duration": 1500, + "energy": 50, + "dps": 23.33 + }, + "40": { + "name": "Blizzard", + "type": "Ice", + "damage": 100, + "duration": 3900, + "energy": 100, + "dps": 25.64 + }, + "131": { + "name": "Body Slam", + "type": "Normal", + "damage": 40, + "duration": 1560, + "energy": 50, + "dps": 25.64 + }, + "22": { + "name": "Megahorn", + "type": "Bug", + "damage": 80, + "duration": 3200, + "energy": 100, + "dps": 25.0 + }, + "122": { + "name": "Hurricane", + "type": "Flying", + "damage": 80, + "duration": 3200, + "energy": 100, + "dps": 25.0 + }, + "116": { + "name": "Solar Beam", + "type": "Grass", + "damage": 120, + "duration": 4900, + "energy": 100, + "dps": 24.48 + }, + "103": { + "name": "Fire Blast", + "type": "Fire", + "damage": 100, + "duration": 4100, + "energy": 100, + "dps": 24.39 + }, + "14": { + "name": "Hyper Beam", + "type": "Normal", + "damage": 120, + "duration": 5000, + "energy": 100, + "dps": 24.0 + }, + "31": { + "name": "Earthquake", + "type": "Ground", + "damage": 100, + "duration": 4200, + "energy": 100, + "dps": 23.8 + }, + "118": { + "name": "Power Whip", + "type": "Grass", + "damage": 70, + "duration": 2800, + "energy": 100, + "dps": 25.0 + }, + "107": { + "name": "Hydro Pump", + "type": "Water", + "damage": 90, + "duration": 3800, + "energy": 100, + "dps": 23.68 + }, + "117": { + "name": "Leaf Blade", + "type": "Grass", + "damage": 55, + "duration": 2800, + "energy": 50, + "dps": 19.64 + }, + "78": { + "name": "Thunder", + "type": "Electric", + "damage": 100, + "duration": 4300, + "energy": 100, + "dps": 23.25 + }, + "123": { + "name": "Brick Break", + "type": "Fighting", + "damage": 30, + "duration": 1600, + "energy": 33, + "dps": 18.75 + }, + "92": { + "name": "Gunk Shot", + "type": "Poison", + "damage": 65, + "duration": 3000, + "energy": 100, + "dps": 21.66 + }, + "90": { + "name": "Sludge Bomb", + "type": "Poison", + "damage": 55, + "duration": 2600, + "energy": 50, + "dps": 21.15 + }, + "42": { + "name": "Heat Wave", + "type": "Fire", + "damage": 80, + "duration": 3800, + "energy": 100, + "dps": 21.05 + }, + "87": { + "name": "Moonblast", + "type": "Fairy", + "damage": 85, + "duration": 4100, + "energy": 100, + "dps": 20.73 + }, + "91": { + "name": "Sludge Wave", + "type": "Poison", + "damage": 70, + "duration": 3400, + "energy": 100, + "dps": 20.58 + }, + "79": { + "name": "Thunderbolt", + "type": "Electric", + "damage": 55, + "duration": 2700, + "energy": 50, + "dps": 20.37 + }, + "47": { + "name": "Petal Blizzard", + "type": "Grass", + "damage": 65, + "duration": 3200, + "energy": 50, + "dps": 20.31 + }, + "89": { + "name": "Cross Poison", + "type": "Poison", + "damage": 25, + "duration": 1500, + "energy": 25, + "dps": 16.66 + }, + "108": { + "name": "Psychic", + "type": "Psychic", + "damage": 55, + "duration": 2800, + "energy": 50, + "dps": 19.64 + }, + "58": { + "name": "Aqua Tail", + "type": "Water", + "damage": 45, + "duration": 2350, + "energy": 50, + "dps": 19.14 + }, + "24": { + "name": "Flamethrower", + "type": "Fire", + "damage": 55, + "duration": 2900, + "energy": 50, + "dps": 18.96 + }, + "88": { + "name": "Play Rough", + "type": "Fairy", + "damage": 55, + "duration": 2900, + "energy": 50, + "dps": 18.96 + }, + "82": { + "name": "Dragon Pulse", + "type": "Dragon", + "damage": 65, + "duration": 3600, + "energy": 50, + "dps": 18.05 + }, + "39": { + "name": "Ice Beam", + "type": "Ice", + "damage": 65, + "duration": 3650, + "energy": 50, + "dps": 17.8 + }, + "49": { + "name": "Bug Buzz", + "type": "Bug", + "damage": 75, + "duration": 4250, + "energy": 50, + "dps": 17.64 + }, + "46": { + "name": "Drill Run", + "type": "Ground", + "damage": 50, + "duration": 3400, + "energy": 33, + "dps": 14.7 + }, + "59": { + "name": "Seed Bomb", + "type": "Grass", + "damage": 40, + "duration": 2400, + "energy": 33, + "dps": 16.66 + }, + "77": { + "name": "Thunder Punch", + "type": "Electric", + "damage": 40, + "duration": 2400, + "energy": 33, + "dps": 16.66 + }, + "100": { + "name": "X Scissor", + "type": "Bug", + "damage": 35, + "duration": 2100, + "energy": 33, + "dps": 16.66 + }, + "129": { + "name": "Hyper Fang", + "type": "Normal", + "damage": 35, + "duration": 2100, + "energy": 33, + "dps": 16.66 + }, + "64": { + "name": "Rock Slide", + "type": "Rock", + "damage": 50, + "duration": 3200, + "energy": 33, + "dps": 15.62 + }, + "94": { + "name": "Bone Club", + "type": "Ground", + "damage": 25, + "duration": 1600, + "energy": 25, + "dps": 15.62 + }, + "36": { + "name": "Flash Cannon", + "type": "Steel", + "damage": 60, + "duration": 3900, + "energy": 33, + "dps": 15.38 + }, + "74": { + "name": "Iron Head", + "type": "Steel", + "damage": 30, + "duration": 2000, + "energy": 33, + "dps": 15.0 + }, + "38": { + "name": "Drill Peck", + "type": "Flying", + "damage": 40, + "duration": 2700, + "energy": 33, + "dps": 14.81 + }, + "60": { + "name": "Psyshock", + "type": "Psychic", + "damage": 40, + "duration": 2700, + "energy": 33, + "dps": 14.81 + }, + "70": { + "name": "Shadow Ball", + "type": "Ghost", + "damage": 45, + "duration": 3080, + "energy": 33, + "dps": 14.61 + }, + "99": { + "name": "Signal Beam", + "type": "Bug", + "damage": 45, + "duration": 3100, + "energy": 33, + "dps": 14.51 + }, + "115": { + "name": "Fire Punch", + "type": "Fire", + "damage": 40, + "duration": 2800, + "energy": 33, + "dps": 14.28 + }, + "54": { + "name": "Submission", + "type": "Fighting", + "damage": 30, + "duration": 2100, + "energy": 33, + "dps": 14.28 + }, + "102": { + "name": "Flame Burst", + "type": "Fire", + "damage": 30, + "duration": 2100, + "energy": 25, + "dps": 14.28 + }, + "127": { + "name": "Stomp", + "type": "Normal", + "damage": 30, + "duration": 2100, + "energy": 25, + "dps": 14.28 + }, + "35": { + "name": "Discharge", + "type": "Electric", + "damage": 35, + "duration": 2500, + "energy": 33, + "dps": 14.0 + }, + "65": { + "name": "Power Gem", + "type": "Rock", + "damage": 40, + "duration": 2900, + "energy": 33, + "dps": 13.79 + }, + "106": { + "name": "Scald", + "type": "Water", + "damage": 55, + "duration": 4000, + "energy": 33, + "dps": 13.75 + }, + "109": { + "name": "Psystrike", + "type": "Psychic", + "damage": 70, + "duration": 5100, + "energy": 100, + "dps": 13.72 + }, + "56": { + "name": "Low Sweep", + "type": "Fighting", + "damage": 30, + "duration": 2250, + "energy": 25, + "dps": 13.33 + }, + "51": { + "name": "Night Slash", + "type": "Dark", + "damage": 30, + "duration": 2700, + "energy": 25, + "dps": 11.11 + }, + "86": { + "name": "Dazzling Gleam", + "type": "Fairy", + "damage": 55, + "duration": 4200, + "energy": 33, + "dps": 13.09 + }, + "16": { + "name": "Dark Pulse", + "type": "Dark", + "damage": 45, + "duration": 3500, + "energy": 33, + "dps": 12.85 + }, + "33": { + "name": "Ice Punch", + "type": "Ice", + "damage": 45, + "duration": 3500, + "energy": 33, + "dps": 12.85 + }, + "26": { + "name": "Dig", + "type": "Ground", + "damage": 70, + "duration": 5800, + "energy": 33, + "dps": 12.06 + }, + "20": { + "name": "Vice Grip", + "type": "Normal", + "damage": 25, + "duration": 2100, + "energy": 20, + "dps": 11.9 + }, + "18": { + "name": "Sludge", + "type": "Poison", + "damage": 30, + "duration": 2600, + "energy": 25, + "dps": 11.53 + }, + "96": { + "name": "Mud Bomb", + "type": "Ground", + "damage": 30, + "duration": 2600, + "energy": 25, + "dps": 11.53 + }, + "126": { + "name": "Horn Attack", + "type": "Normal", + "damage": 25, + "duration": 2200, + "energy": 25, + "dps": 11.36 + }, + "121": { + "name": "Air Cutter", + "type": "Flying", + "damage": 30, + "duration": 3300, + "energy": 25, + "dps": 9.09 + }, + "132": { + "name": "Rest", + "type": "Normal", + "damage": 35, + "duration": 3100, + "energy": 33, + "dps": 11.29 + }, + "72": { + "name": "Magnet Bomb", + "type": "Steel", + "damage": 30, + "duration": 2800, + "energy": 25, + "dps": 10.71 + }, + "57": { + "name": "Aqua Jet", + "type": "Water", + "damage": 25, + "duration": 2350, + "energy": 20, + "dps": 10.63 + }, + "105": { + "name": "Water Pulse", + "type": "Water", + "damage": 35, + "duration": 3300, + "energy": 25, + "dps": 10.6 + }, + "30": { + "name": "Psybeam", + "type": "Psychic", + "damage": 40, + "duration": 3800, + "energy": 25, + "dps": 10.52 + }, + "63": { + "name": "Rock Tomb", + "type": "Rock", + "damage": 30, + "duration": 3400, + "energy": 25, + "dps": 8.82 + }, + "50": { + "name": "Poison Fang", + "type": "Poison", + "damage": 25, + "duration": 2400, + "energy": 20, + "dps": 10.41 + }, + "104": { + "name": "Brine", + "type": "Water", + "damage": 25, + "duration": 2400, + "energy": 25, + "dps": 10.41 + }, + "45": { + "name": "Aerial Ace", + "type": "Flying", + "damage": 30, + "duration": 2900, + "energy": 25, + "dps": 10.34 + }, + "53": { + "name": "Bubble Beam", + "type": "Water", + "damage": 30, + "duration": 2900, + "energy": 25, + "dps": 10.34 + }, + "95": { + "name": "Bulldoze", + "type": "Ground", + "damage": 35, + "duration": 3400, + "energy": 25, + "dps": 10.29 + }, + "125": { + "name": "Swift", + "type": "Normal", + "damage": 30, + "duration": 3000, + "energy": 25, + "dps": 10.0 + }, + "62": { + "name": "Ancient Power", + "type": "Rock", + "damage": 35, + "duration": 3600, + "energy": 25, + "dps": 9.72 + }, + "114": { + "name": "Giga Drain", + "type": "Grass", + "damage": 35, + "duration": 3600, + "energy": 33, + "dps": 9.72 + }, + "69": { + "name": "Ominous Wind", + "type": "Ghost", + "damage": 30, + "duration": 3100, + "energy": 25, + "dps": 9.67 + }, + "67": { + "name": "Shadow Punch", + "type": "Ghost", + "damage": 20, + "duration": 2100, + "energy": 25, + "dps": 9.52 + }, + "80": { + "name": "Twister", + "type": "Dragon", + "damage": 25, + "duration": 2700, + "energy": 20, + "dps": 9.25 + }, + "85": { + "name": "Draining Kiss", + "type": "Fairy", + "damage": 25, + "duration": 2800, + "energy": 20, + "dps": 8.92 + }, + "21": { + "name": "Flame Wheel", + "type": "Fire", + "damage": 40, + "duration": 4600, + "energy": 25, + "dps": 8.69 + }, + "133": { + "name": "Struggle", + "type": "Normal", + "damage": 15, + "duration": 1695, + "energy": 20, + "dps": 8.84 + }, + "101": { + "name": "Flame Charge", + "type": "Fire", + "damage": 25, + "duration": 3100, + "energy": 20, + "dps": 8.06 + }, + "34": { + "name": "Heart Stamp", + "type": "Psychic", + "damage": 20, + "duration": 2550, + "energy": 25, + "dps": 7.84 + }, + "75": { + "name": "Parabolic Charge", + "type": "Electric", + "damage": 15, + "duration": 2100, + "energy": 20, + "dps": 7.14 + }, + "111": { + "name": "Icy Wind", + "type": "Ice", + "damage": 25, + "duration": 3800, + "energy": 20, + "dps": 6.57 + }, + "84": { + "name": "Disarming Voice", + "type": "Fairy", + "damage": 25, + "duration": 3900, + "energy": 20, + "dps": 6.41 + }, + "13": { + "name": "Wrap", + "type": "Normal", + "damage": 25, + "duration": 4000, + "energy": 20, + "dps": 6.25 + }, + "66": { + "name": "Shadow Sneak", + "type": "Ghost", + "damage": 15, + "duration": 3100, + "energy": 20, + "dps": 4.83 + }, + "48": { + "name": "Mega Drain", + "type": "Grass", + "damage": 15, + "duration": 3200, + "energy": 20, + "dps": 4.68 + } +} \ No newline at end of file diff --git a/static/data/pokemon.json b/static/data/pokemon.json index bf3303d476..3b482969ce 100644 --- a/static/data/pokemon.json +++ b/static/data/pokemon.json @@ -1811,7 +1811,7 @@ }, "143": { "name": "Snorlax", - "rarity": "Rare", + "rarity": "Very Rare", "types": [ { "type": "Normal", diff --git a/static/forts/Harmony.png b/static/forts/Harmony.png index bb4e74e26a..8a7befe3a9 100644 Binary files a/static/forts/Harmony.png and b/static/forts/Harmony.png differ diff --git a/static/forts/Harmony_large.png b/static/forts/Harmony_large.png index a18aeb23a7..448d56fd87 100644 Binary files a/static/forts/Harmony_large.png and b/static/forts/Harmony_large.png differ diff --git a/static/forts/Instinct.png b/static/forts/Instinct.png index bb26b6891e..b1f830d8c8 100644 Binary files a/static/forts/Instinct.png and b/static/forts/Instinct.png differ diff --git a/static/forts/Instinct_large.png b/static/forts/Instinct_large.png index 5bfd23d1ab..24e0424489 100644 Binary files a/static/forts/Instinct_large.png and b/static/forts/Instinct_large.png differ diff --git a/static/forts/Mystic.png b/static/forts/Mystic.png index 11cc117f59..279a748f7a 100644 Binary files a/static/forts/Mystic.png and b/static/forts/Mystic.png differ diff --git a/static/forts/Mystic_large.png b/static/forts/Mystic_large.png index 208cd38488..97cdf95dd8 100644 Binary files a/static/forts/Mystic_large.png and b/static/forts/Mystic_large.png differ diff --git a/static/forts/Uncontested.png b/static/forts/Uncontested.png index cfaded8bd8..c319a14a69 100644 Binary files a/static/forts/Uncontested.png and b/static/forts/Uncontested.png differ diff --git a/static/forts/Uncontested_large.png b/static/forts/Uncontested_large.png index 4d38727b7b..f3dd4534d2 100644 Binary files a/static/forts/Uncontested_large.png and b/static/forts/Uncontested_large.png differ diff --git a/static/forts/Valor.png b/static/forts/Valor.png index 54b01eca5f..0f48a85489 100644 Binary files a/static/forts/Valor.png and b/static/forts/Valor.png differ diff --git a/static/forts/Valor_large.png b/static/forts/Valor_large.png index cbb56ed288..6dbc03d1cd 100644 Binary files a/static/forts/Valor_large.png and b/static/forts/Valor_large.png differ diff --git a/static/forts/ingame/Harmony.png b/static/forts/ingame/Harmony.png new file mode 100644 index 0000000000..4e25978405 Binary files /dev/null and b/static/forts/ingame/Harmony.png differ diff --git a/static/forts/ingame/Instinct.png b/static/forts/ingame/Instinct.png new file mode 100644 index 0000000000..3e3e2e23aa Binary files /dev/null and b/static/forts/ingame/Instinct.png differ diff --git a/static/forts/ingame/Instinct_1.png b/static/forts/ingame/Instinct_1.png new file mode 100644 index 0000000000..13d326bbad Binary files /dev/null and b/static/forts/ingame/Instinct_1.png differ diff --git a/static/forts/ingame/Instinct_10.png b/static/forts/ingame/Instinct_10.png new file mode 100644 index 0000000000..63a0cd3804 Binary files /dev/null and b/static/forts/ingame/Instinct_10.png differ diff --git a/static/forts/ingame/Instinct_2.png b/static/forts/ingame/Instinct_2.png new file mode 100644 index 0000000000..364c954b77 Binary files /dev/null and b/static/forts/ingame/Instinct_2.png differ diff --git a/static/forts/ingame/Instinct_3.png b/static/forts/ingame/Instinct_3.png new file mode 100644 index 0000000000..336c49a378 Binary files /dev/null and b/static/forts/ingame/Instinct_3.png differ diff --git a/static/forts/ingame/Instinct_4.png b/static/forts/ingame/Instinct_4.png new file mode 100644 index 0000000000..2789eb0ce9 Binary files /dev/null and b/static/forts/ingame/Instinct_4.png differ diff --git a/static/forts/ingame/Instinct_5.png b/static/forts/ingame/Instinct_5.png new file mode 100644 index 0000000000..47b1cd3b8b Binary files /dev/null and b/static/forts/ingame/Instinct_5.png differ diff --git a/static/forts/ingame/Instinct_6.png b/static/forts/ingame/Instinct_6.png new file mode 100644 index 0000000000..0d471909d4 Binary files /dev/null and b/static/forts/ingame/Instinct_6.png differ diff --git a/static/forts/ingame/Instinct_7.png b/static/forts/ingame/Instinct_7.png new file mode 100644 index 0000000000..5a71683188 Binary files /dev/null and b/static/forts/ingame/Instinct_7.png differ diff --git a/static/forts/ingame/Instinct_8.png b/static/forts/ingame/Instinct_8.png new file mode 100644 index 0000000000..572e955742 Binary files /dev/null and b/static/forts/ingame/Instinct_8.png differ diff --git a/static/forts/ingame/Instinct_9.png b/static/forts/ingame/Instinct_9.png new file mode 100644 index 0000000000..9edcd11763 Binary files /dev/null and b/static/forts/ingame/Instinct_9.png differ diff --git a/static/forts/ingame/Mystic.png b/static/forts/ingame/Mystic.png new file mode 100644 index 0000000000..6323c6d546 Binary files /dev/null and b/static/forts/ingame/Mystic.png differ diff --git a/static/forts/ingame/Mystic_1.png b/static/forts/ingame/Mystic_1.png new file mode 100644 index 0000000000..e4ed513b1f Binary files /dev/null and b/static/forts/ingame/Mystic_1.png differ diff --git a/static/forts/ingame/Mystic_10.png b/static/forts/ingame/Mystic_10.png new file mode 100644 index 0000000000..505c9a92b2 Binary files /dev/null and b/static/forts/ingame/Mystic_10.png differ diff --git a/static/forts/ingame/Mystic_2.png b/static/forts/ingame/Mystic_2.png new file mode 100644 index 0000000000..029fc30e14 Binary files /dev/null and b/static/forts/ingame/Mystic_2.png differ diff --git a/static/forts/ingame/Mystic_3.png b/static/forts/ingame/Mystic_3.png new file mode 100644 index 0000000000..150d7458e2 Binary files /dev/null and b/static/forts/ingame/Mystic_3.png differ diff --git a/static/forts/ingame/Mystic_4.png b/static/forts/ingame/Mystic_4.png new file mode 100644 index 0000000000..1a1e417ba0 Binary files /dev/null and b/static/forts/ingame/Mystic_4.png differ diff --git a/static/forts/ingame/Mystic_5.png b/static/forts/ingame/Mystic_5.png new file mode 100644 index 0000000000..d41a80b3f0 Binary files /dev/null and b/static/forts/ingame/Mystic_5.png differ diff --git a/static/forts/ingame/Mystic_6.png b/static/forts/ingame/Mystic_6.png new file mode 100644 index 0000000000..308a17ef35 Binary files /dev/null and b/static/forts/ingame/Mystic_6.png differ diff --git a/static/forts/ingame/Mystic_7.png b/static/forts/ingame/Mystic_7.png new file mode 100644 index 0000000000..58d0c4d2e9 Binary files /dev/null and b/static/forts/ingame/Mystic_7.png differ diff --git a/static/forts/ingame/Mystic_8.png b/static/forts/ingame/Mystic_8.png new file mode 100644 index 0000000000..3ac43ff42a Binary files /dev/null and b/static/forts/ingame/Mystic_8.png differ diff --git a/static/forts/ingame/Mystic_9.png b/static/forts/ingame/Mystic_9.png new file mode 100644 index 0000000000..c2f26ab2b9 Binary files /dev/null and b/static/forts/ingame/Mystic_9.png differ diff --git a/static/forts/ingame/Uncontested.png b/static/forts/ingame/Uncontested.png new file mode 100644 index 0000000000..f212da33f5 Binary files /dev/null and b/static/forts/ingame/Uncontested.png differ diff --git a/static/forts/ingame/Valor.png b/static/forts/ingame/Valor.png new file mode 100644 index 0000000000..a91f54a452 Binary files /dev/null and b/static/forts/ingame/Valor.png differ diff --git a/static/forts/ingame/Valor_1.png b/static/forts/ingame/Valor_1.png new file mode 100644 index 0000000000..b2a7942cb4 Binary files /dev/null and b/static/forts/ingame/Valor_1.png differ diff --git a/static/forts/ingame/Valor_10.png b/static/forts/ingame/Valor_10.png new file mode 100644 index 0000000000..798198220d Binary files /dev/null and b/static/forts/ingame/Valor_10.png differ diff --git a/static/forts/ingame/Valor_2.png b/static/forts/ingame/Valor_2.png new file mode 100644 index 0000000000..6fdfc4bfac Binary files /dev/null and b/static/forts/ingame/Valor_2.png differ diff --git a/static/forts/ingame/Valor_3.png b/static/forts/ingame/Valor_3.png new file mode 100644 index 0000000000..037f992a4a Binary files /dev/null and b/static/forts/ingame/Valor_3.png differ diff --git a/static/forts/ingame/Valor_4.png b/static/forts/ingame/Valor_4.png new file mode 100644 index 0000000000..c496158aa1 Binary files /dev/null and b/static/forts/ingame/Valor_4.png differ diff --git a/static/forts/ingame/Valor_5.png b/static/forts/ingame/Valor_5.png new file mode 100644 index 0000000000..9a3939d9ae Binary files /dev/null and b/static/forts/ingame/Valor_5.png differ diff --git a/static/forts/ingame/Valor_6.png b/static/forts/ingame/Valor_6.png new file mode 100644 index 0000000000..24f69d027f Binary files /dev/null and b/static/forts/ingame/Valor_6.png differ diff --git a/static/forts/ingame/Valor_7.png b/static/forts/ingame/Valor_7.png new file mode 100644 index 0000000000..62b524ccb1 Binary files /dev/null and b/static/forts/ingame/Valor_7.png differ diff --git a/static/forts/ingame/Valor_8.png b/static/forts/ingame/Valor_8.png new file mode 100644 index 0000000000..81935edb38 Binary files /dev/null and b/static/forts/ingame/Valor_8.png differ diff --git a/static/forts/ingame/Valor_9.png b/static/forts/ingame/Valor_9.png new file mode 100644 index 0000000000..ad43a129fa Binary files /dev/null and b/static/forts/ingame/Valor_9.png differ diff --git a/static/forts/shield/Harmony.png b/static/forts/shield/Harmony.png new file mode 100644 index 0000000000..8a7befe3a9 Binary files /dev/null and b/static/forts/shield/Harmony.png differ diff --git a/static/forts/shield/Instinct.png b/static/forts/shield/Instinct.png new file mode 100644 index 0000000000..b1f830d8c8 Binary files /dev/null and b/static/forts/shield/Instinct.png differ diff --git a/static/forts/shield/Instinct_1.png b/static/forts/shield/Instinct_1.png new file mode 100644 index 0000000000..d78997f426 Binary files /dev/null and b/static/forts/shield/Instinct_1.png differ diff --git a/static/forts/shield/Instinct_10.png b/static/forts/shield/Instinct_10.png new file mode 100644 index 0000000000..bf597371ff Binary files /dev/null and b/static/forts/shield/Instinct_10.png differ diff --git a/static/forts/shield/Instinct_2.png b/static/forts/shield/Instinct_2.png new file mode 100644 index 0000000000..3021526538 Binary files /dev/null and b/static/forts/shield/Instinct_2.png differ diff --git a/static/forts/shield/Instinct_3.png b/static/forts/shield/Instinct_3.png new file mode 100644 index 0000000000..e885ba4d18 Binary files /dev/null and b/static/forts/shield/Instinct_3.png differ diff --git a/static/forts/shield/Instinct_4.png b/static/forts/shield/Instinct_4.png new file mode 100644 index 0000000000..bd2c7605ab Binary files /dev/null and b/static/forts/shield/Instinct_4.png differ diff --git a/static/forts/shield/Instinct_5.png b/static/forts/shield/Instinct_5.png new file mode 100644 index 0000000000..79bd2a96ac Binary files /dev/null and b/static/forts/shield/Instinct_5.png differ diff --git a/static/forts/shield/Instinct_6.png b/static/forts/shield/Instinct_6.png new file mode 100644 index 0000000000..8194935751 Binary files /dev/null and b/static/forts/shield/Instinct_6.png differ diff --git a/static/forts/shield/Instinct_7.png b/static/forts/shield/Instinct_7.png new file mode 100644 index 0000000000..271b6770dd Binary files /dev/null and b/static/forts/shield/Instinct_7.png differ diff --git a/static/forts/shield/Instinct_8.png b/static/forts/shield/Instinct_8.png new file mode 100644 index 0000000000..d9abea7b79 Binary files /dev/null and b/static/forts/shield/Instinct_8.png differ diff --git a/static/forts/shield/Instinct_9.png b/static/forts/shield/Instinct_9.png new file mode 100644 index 0000000000..7b96e7094f Binary files /dev/null and b/static/forts/shield/Instinct_9.png differ diff --git a/static/forts/shield/Mystic.png b/static/forts/shield/Mystic.png new file mode 100644 index 0000000000..279a748f7a Binary files /dev/null and b/static/forts/shield/Mystic.png differ diff --git a/static/forts/shield/Mystic_1.png b/static/forts/shield/Mystic_1.png new file mode 100644 index 0000000000..2f404f741c Binary files /dev/null and b/static/forts/shield/Mystic_1.png differ diff --git a/static/forts/shield/Mystic_10.png b/static/forts/shield/Mystic_10.png new file mode 100644 index 0000000000..76fa8b7e62 Binary files /dev/null and b/static/forts/shield/Mystic_10.png differ diff --git a/static/forts/shield/Mystic_2.png b/static/forts/shield/Mystic_2.png new file mode 100644 index 0000000000..98691f0d45 Binary files /dev/null and b/static/forts/shield/Mystic_2.png differ diff --git a/static/forts/shield/Mystic_3.png b/static/forts/shield/Mystic_3.png new file mode 100644 index 0000000000..f041f8456c Binary files /dev/null and b/static/forts/shield/Mystic_3.png differ diff --git a/static/forts/shield/Mystic_4.png b/static/forts/shield/Mystic_4.png new file mode 100644 index 0000000000..675874e2f4 Binary files /dev/null and b/static/forts/shield/Mystic_4.png differ diff --git a/static/forts/shield/Mystic_5.png b/static/forts/shield/Mystic_5.png new file mode 100644 index 0000000000..e6d374c037 Binary files /dev/null and b/static/forts/shield/Mystic_5.png differ diff --git a/static/forts/shield/Mystic_6.png b/static/forts/shield/Mystic_6.png new file mode 100644 index 0000000000..fbfde9ef30 Binary files /dev/null and b/static/forts/shield/Mystic_6.png differ diff --git a/static/forts/shield/Mystic_7.png b/static/forts/shield/Mystic_7.png new file mode 100644 index 0000000000..46cccfe133 Binary files /dev/null and b/static/forts/shield/Mystic_7.png differ diff --git a/static/forts/shield/Mystic_8.png b/static/forts/shield/Mystic_8.png new file mode 100644 index 0000000000..b6f95d8ae1 Binary files /dev/null and b/static/forts/shield/Mystic_8.png differ diff --git a/static/forts/shield/Mystic_9.png b/static/forts/shield/Mystic_9.png new file mode 100644 index 0000000000..7b4e5c99ec Binary files /dev/null and b/static/forts/shield/Mystic_9.png differ diff --git a/static/forts/shield/Uncontested.png b/static/forts/shield/Uncontested.png new file mode 100644 index 0000000000..c319a14a69 Binary files /dev/null and b/static/forts/shield/Uncontested.png differ diff --git a/static/forts/shield/Valor.png b/static/forts/shield/Valor.png new file mode 100644 index 0000000000..0f48a85489 Binary files /dev/null and b/static/forts/shield/Valor.png differ diff --git a/static/forts/shield/Valor_1.png b/static/forts/shield/Valor_1.png new file mode 100644 index 0000000000..736184e8ea Binary files /dev/null and b/static/forts/shield/Valor_1.png differ diff --git a/static/forts/shield/Valor_10.png b/static/forts/shield/Valor_10.png new file mode 100644 index 0000000000..f259508c6a Binary files /dev/null and b/static/forts/shield/Valor_10.png differ diff --git a/static/forts/shield/Valor_2.png b/static/forts/shield/Valor_2.png new file mode 100644 index 0000000000..eb89a5ff51 Binary files /dev/null and b/static/forts/shield/Valor_2.png differ diff --git a/static/forts/shield/Valor_3.png b/static/forts/shield/Valor_3.png new file mode 100644 index 0000000000..0224507625 Binary files /dev/null and b/static/forts/shield/Valor_3.png differ diff --git a/static/forts/shield/Valor_4.png b/static/forts/shield/Valor_4.png new file mode 100644 index 0000000000..708e81c5bb Binary files /dev/null and b/static/forts/shield/Valor_4.png differ diff --git a/static/forts/shield/Valor_5.png b/static/forts/shield/Valor_5.png new file mode 100644 index 0000000000..fb4335c028 Binary files /dev/null and b/static/forts/shield/Valor_5.png differ diff --git a/static/forts/shield/Valor_6.png b/static/forts/shield/Valor_6.png new file mode 100644 index 0000000000..2e33164975 Binary files /dev/null and b/static/forts/shield/Valor_6.png differ diff --git a/static/forts/shield/Valor_7.png b/static/forts/shield/Valor_7.png new file mode 100644 index 0000000000..058eb97c3a Binary files /dev/null and b/static/forts/shield/Valor_7.png differ diff --git a/static/forts/shield/Valor_8.png b/static/forts/shield/Valor_8.png new file mode 100644 index 0000000000..9962b58df0 Binary files /dev/null and b/static/forts/shield/Valor_8.png differ diff --git a/static/forts/shield/Valor_9.png b/static/forts/shield/Valor_9.png new file mode 100644 index 0000000000..ce5bea9e60 Binary files /dev/null and b/static/forts/shield/Valor_9.png differ diff --git a/static/js/app.js b/static/js/app.js index b6a8470c7a..2d1fb7508b 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2,6 +2,20 @@ 'use strict' // Methods/polyfills. + if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s) + var i = matches.length + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + } + } // addEventsListener var addEventsListener = function (o, t, e) { diff --git a/static/js/map.common.js b/static/js/map.common.js index 932cac3067..0c82104ad6 100644 --- a/static/js/map.common.js +++ b/static/js/map.common.js @@ -688,6 +688,10 @@ var StoreOptions = { default: [], type: StoreTypes.JSON }, + 'remember_text_perfection_notify': { + default: '', + type: StoreTypes.Number + }, 'showGyms': { default: false, type: StoreTypes.Boolean @@ -756,6 +760,10 @@ var StoreOptions = { default: 'none', type: StoreTypes.String }, + 'gymMarkerStyle': { + default: 'shield', + type: StoreTypes.String + }, 'zoomLevel': { default: 16, type: StoreTypes.Number @@ -850,3 +858,8 @@ function isTouchDevice () { // Should cover most browsers return 'ontouchstart' in window || navigator.maxTouchPoints } + +function isMobileDevice () { + // Basic mobile OS (not browser) detection + return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) +} diff --git a/static/js/map.js b/static/js/map.js index 5b76c50377..e367bd0e3c 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -1,15 +1,17 @@ -// +// // Global map.js variables // var $selectExclude var $selectPokemonNotify var $selectRarityNotify +var $textPerfectionNotify var $selectStyle var $selectIconResolution var $selectIconSize var $selectLuredPokestopsOnly var $selectSearchIconMarker +var $selectGymMarkerStyle var $selectLocationIconMarker var language = document.documentElement.lang === '' ? 'en' : document.documentElement.lang @@ -23,6 +25,7 @@ var searchMarkerStyles var excludedPokemon = [] var notifiedPokemon = [] var notifiedRarity = [] +var notifiedMinPerfection = null var map var rawDataIsLoading = false @@ -31,14 +34,18 @@ var rangeMarkers = ['pokemon', 'pokestop', 'gym'] var searchMarker var storeZoom = true var scanPath - +var moves var selectedStyle = 'light' +var updateWorker +var lastUpdateTime var gymTypes = ['Uncontested', 'Mystic', 'Valor', 'Instinct'] +var gymPrestige = [2000, 4000, 8000, 12000, 16000, 20000, 30000, 40000, 50000] var audio = new Audio('static/sounds/ding.mp3') + // // Functions // @@ -307,14 +314,25 @@ function openMapDirections (lat, lng) { // eslint-disable-line no-unused-vars window.open(url, '_blank') } -function pokemonLabel (name, rarity, types, disappearTime, id, latitude, longitude, encounterId) { +function pokemonLabel (name, rarity, types, disappearTime, id, latitude, longitude, encounterId, atk, def, sta, move1, move2) { var disappearDate = new Date(disappearTime) var rarityDisplay = rarity ? '(' + rarity + ')' : '' var typesDisplay = '' $.each(types, function (index, type) { typesDisplay += getTypeSpan(type) }) - + var details = '' + if (atk != null) { + var iv = (atk + def + sta) / 45 * 100 + details = ` +
+ IV: ${iv.toFixed(1)}% (${atk}/${def}/${sta}) +
+
+ Moves: ${i8ln(moves[move1]['name'])} / ${i8ln(moves[move2]['name'])} +
+ ` + } var contentstring = `
${name} @@ -333,6 +351,7 @@ function pokemonLabel (name, rarity, types, disappearTime, id, latitude, longitu
Location: ${latitude.toFixed(6)}, ${longitude.toFixed(7)}
+ ${details}
Exclude   Notify   @@ -385,11 +404,7 @@ function gymLabel (teamName, teamId, gymPoints, latitude, longitude, lastScanned
` } else { - var gymPrestige = [2000, 4000, 8000, 12000, 16000, 20000, 30000, 40000, 50000] - var gymLevel = 1 - while (gymPoints >= gymPrestige[gymLevel - 1]) { - gymLevel++ - } + var gymLevel = getGymLevel(gymPoints) str = `
@@ -425,6 +440,15 @@ function gymLabel (teamName, teamId, gymPoints, latitude, longitude, lastScanned return str } +function getGymLevel (points) { + var level = 1 + while (points >= gymPrestige[level - 1]) { + level++ + } + + return level +} + function pokestopLabel (expireTime, latitude, longitude) { var str if (expireTime) { @@ -541,7 +565,7 @@ function customizePokemonMarker (marker, item, skipNotification) { } marker.infoWindow = new google.maps.InfoWindow({ - content: pokemonLabel(item['pokemon_name'], item['pokemon_rarity'], item['pokemon_types'], item['disappear_time'], item['pokemon_id'], item['latitude'], item['longitude'], item['encounter_id']), + content: pokemonLabel(item['pokemon_name'], item['pokemon_rarity'], item['pokemon_types'], item['disappear_time'], item['pokemon_id'], item['latitude'], item['longitude'], item['encounter_id'], item['individual_attack'], item['individual_defense'], item['individual_stamina'], item['move_1'], item['move_2']), disableAutoPan: true }) @@ -557,6 +581,21 @@ function customizePokemonMarker (marker, item, skipNotification) { } } + if (item['individual_attack'] != null) { + var perfection = 100.0 * (item['individual_attack'] + item['individual_defense'] + item['individual_stamina']) / 45 + if (notifiedMinPerfection > 0 && perfection >= notifiedMinPerfection) { + if (!skipNotification) { + if (Store.get('playSound')) { + audio.play() + } + sendNotification('A ' + perfection.toFixed(1) + '% perfect ' + item['pokemon_name'] + ' appeared!', 'Click to load map', 'static/icons/' + item['pokemon_id'] + '.png', item['latitude'], item['longitude']) + } + if (marker.animationDisabled !== true) { + marker.setAnimation(google.maps.Animation.BOUNCE) + } + } + } + addListeners(marker) } @@ -567,7 +606,7 @@ function setupGymMarker (item) { lng: item['longitude'] }, map: map, - icon: 'static/forts/' + gymTypes[item['team_id']] + '.png' + icon: {url: 'static/forts/' + Store.get('gymMarkerStyle') + '/' + gymTypes[item['team_id']] + (item['team_id'] !== 0 ? '_' + getGymLevel(item['gym_points']) : '') + '.png', scaledSize: new google.maps.Size(48, 48)} }) if (!marker.rangeCircle && isRangeActive(map)) { @@ -584,10 +623,15 @@ function setupGymMarker (item) { } function updateGymMarker (item, marker) { - marker.setIcon('static/forts/' + gymTypes[item['team_id']] + '.png') + marker.setIcon({url: 'static/forts/' + Store.get('gymMarkerStyle') + '/' + gymTypes[item['team_id']] + (item['team_id'] !== 0 ? '_' + getGymLevel(item['gym_points']) : '') + '.png', scaledSize: new google.maps.Size(48, 48)}) marker.infoWindow.setContent(gymLabel(gymTypes[item['team_id']], item['team_id'], item['gym_points'], item['latitude'], item['longitude'], item['last_scanned'], item['name'], item['pokemon'])) return marker } +function updateGymIcons () { + $.each(mapData.gyms, function (key, value) { + mapData.gyms[key]['marker'].setIcon({url: 'static/forts/' + Store.get('gymMarkerStyle') + '/' + gymTypes[mapData.gyms[key]['team_id']] + (mapData.gyms[key]['team_id'] !== 0 ? '_' + getGymLevel(mapData.gyms[key]['gym_points']) : '') + '.png', scaledSize: new google.maps.Size(48, 48)}) + }) +} function setupPokestopMarker (item) { var imagename = item['lure_expiration'] ? 'PstopLured' : 'Pstop' @@ -738,10 +782,17 @@ function clearSelection () { function addListeners (marker) { marker.addListener('click', function () { - marker.infoWindow.open(map, marker) - clearSelection() - updateLabelDiffTime() - marker.persist = true + if (!marker.infoWindowIsOpen) { + marker.infoWindow.open(map, marker) + clearSelection() + updateLabelDiffTime() + marker.persist = true + marker.infoWindowIsOpen = true + } else { + marker.persist = null + marker.infoWindow.close() + marker.infoWindowIsOpen = false + } }) google.maps.event.addListener(marker.infoWindow, 'closeclick', function () { @@ -1015,8 +1066,9 @@ function updateMap () { // drawScanPath(result.scanned); clearStaleMarkers() if ($('#stats').hasClass('visible')) { - countMarkers() + countMarkers(map) } + lastUpdateTime = Date.now() }) } @@ -1220,6 +1272,62 @@ function i8ln (word) { } } +function updateGeoLocation () { + if (navigator.geolocation && (Store.get('geoLocate') || Store.get('followMyLocation'))) { + navigator.geolocation.getCurrentPosition(function (position) { + var lat = position.coords.latitude + var lng = position.coords.longitude + var center = new google.maps.LatLng(lat, lng) + + if (Store.get('geoLocate')) { + // the search function makes any small movements cause a loop. Need to increase resolution + if ((typeof searchMarker !== 'undefined') && (getPointDistance(searchMarker.getPosition(), center) > 40)) { + $.post('next_loc?lat=' + lat + '&lon=' + lng).done(function () { + map.panTo(center) + searchMarker.setPosition(center) + }) + } + } + if (Store.get('followMyLocation')) { + if ((typeof locationMarker !== 'undefined') && (getPointDistance(locationMarker.getPosition(), center) >= 5)) { + map.panTo(center) + locationMarker.setPosition(center) + Store.set('followMyLocationPosition', { lat: lat, lng: lng }) + } + } + }) + } +} + +function createUpdateWorker () { + try { + if (isMobileDevice() && (window.Worker)) { + var updateBlob = new Blob([`onmessage = function(e) { + var data = e.data + if (data.name === 'backgroundUpdate') { + self.setInterval(function () {self.postMessage({name: 'backgroundUpdate'})}, 5000) + } + }`]) + + var updateBlobURL = window.URL.createObjectURL(updateBlob) + + updateWorker = new Worker(updateBlobURL) + + updateWorker.onmessage = function (e) { + var data = e.data + if (document.hidden && data.name === 'backgroundUpdate' && Date.now() - lastUpdateTime > 2500) { + updateMap() + updateGeoLocation() + } + } + + updateWorker.postMessage({name: 'backgroundUpdate'}) + } + } catch (ex) { + console.log('Webworker error: ' + ex.message) + } +} + // // Page Ready Exection // @@ -1349,6 +1457,20 @@ $(function () { $selectLocationIconMarker.val(Store.get('locationMarkerStyle')).trigger('change') }) + + $selectGymMarkerStyle = $('#gym-marker-style') + + $selectGymMarkerStyle.select2({ + placeholder: 'Select Style', + minimumResultsForSearch: Infinity + }) + + $selectGymMarkerStyle.on('change', function (e) { + Store.set('gymMarkerStyle', this.value) + updateGymIcons() + }) + + $selectGymMarkerStyle.val(Store.get('gymMarkerStyle')).trigger('change') }) $(function () { @@ -1366,9 +1488,14 @@ $(function () { centerMapOnLocation() } + $.getJSON('static/dist/data/moves.min.json').done(function (data) { + moves = data + }) + $selectExclude = $('#exclude-pokemon') $selectPokemonNotify = $('#notify-pokemon') $selectRarityNotify = $('#notify-rarity') + $textPerfectionNotify = $('#notify-perfection') var numberOfPokemon = 151 // Load pokemon names and populate lists @@ -1427,13 +1554,25 @@ $(function () { notifiedRarity = $selectRarityNotify.val().map(String) Store.set('remember_select_rarity_notify', notifiedRarity) }) + $textPerfectionNotify.on('change', function (e) { + notifiedMinPerfection = parseInt($textPerfectionNotify.val(), 10) + if (isNaN(notifiedMinPerfection) || notifiedMinPerfection <= 0) { + notifiedMinPerfection = '' + } + if (notifiedMinPerfection > 100) { + notifiedMinPerfection = 100 + } + $textPerfectionNotify.val(notifiedMinPerfection) + Store.set('remember_text_perfection_notify', notifiedMinPerfection) + }) // recall saved lists $selectExclude.val(Store.get('remember_select_exclude')).trigger('change') $selectPokemonNotify.val(Store.get('remember_select_notify')).trigger('change') $selectRarityNotify.val(Store.get('remember_select_rarity_notify')).trigger('change') + $textPerfectionNotify.val(Store.get('remember_text_perfection_notify')).trigger('change') - if (isTouchDevice()) { + if (isTouchDevice() && isMobileDevice()) { $('.select2-search input').prop('readonly', true) } }) @@ -1441,32 +1580,9 @@ $(function () { // run interval timers to regularly update map and timediffs window.setInterval(updateLabelDiffTime, 1000) window.setInterval(updateMap, 5000) - window.setInterval(function () { - if (navigator.geolocation && (Store.get('geoLocate') || Store.get('followMyLocation'))) { - navigator.geolocation.getCurrentPosition(function (position) { - var lat = position.coords.latitude - var lng = position.coords.longitude - var center = new google.maps.LatLng(lat, lng) - - if (Store.get('geoLocate')) { - // the search function makes any small movements cause a loop. Need to increase resolution - if ((typeof searchMarker !== 'undefined') && (getPointDistance(searchMarker.getPosition(), center) > 40)) { - $.post('next_loc?lat=' + lat + '&lon=' + lng).done(function () { - map.panTo(center) - searchMarker.setPosition(center) - }) - } - } - if (Store.get('followMyLocation')) { - if ((typeof locationMarker !== 'undefined') && (getPointDistance(locationMarker.getPosition(), center) >= 5)) { - map.panTo(center) - locationMarker.setPosition(center) - Store.set('followMyLocationPosition', { lat: lat, lng: lng }) - } - } - }) - } - }, 1000) + window.setInterval(updateGeoLocation, 1000) + + createUpdateWorker() // Wipe off/restore map icons when switches are toggled function buildSwitchChangeListener (data, dataType, storageKey) { @@ -1556,4 +1672,24 @@ $(function () { heightStyle: 'content' }) } + + // Initialize dataTable in statistics sidebar + // - turn off sorting for the 'icon' column + // - initially sort 'name' column alphabetically + + $('#pokemonList_table').DataTable({ + paging: false, + searching: false, + info: false, + errMode: 'throw', + 'language': { + 'emptyTable': '' + }, + 'columns': [ + { 'orderable': false }, + null, + null, + null + ] + }).order([1, 'asc']) }) diff --git a/static/js/statistics.js b/static/js/statistics.js index bf37bf9a29..c7c3a96c79 100644 --- a/static/js/statistics.js +++ b/static/js/statistics.js @@ -116,10 +116,10 @@ function processSeen (seen) { return a['pokemon_id'] - b['pokemon_id'] } else if (sort.options[sort.selectedIndex].value === 'name') { if (a['pokemon_name'].toLowerCase() < b['pokemon_name'].toLowerCase()) { - return 1 + return -1 } if (a['pokemon_name'].toLowerCase() > b['pokemon_name'].toLowerCase()) { - return -1 + return 1 } return 0 } else { diff --git a/static/js/stats.js b/static/js/stats.js index a8e708e32c..bc334cc2ba 100644 --- a/static/js/stats.js +++ b/static/js/stats.js @@ -1,4 +1,5 @@ -function countMarkers () { // eslint-disable-line no-unused-vars +function countMarkers (map) { // eslint-disable-line no-unused-vars + document.getElementById('stats-ldg-label').innerHTML = '' document.getElementById('stats-pkmn-label').innerHTML = 'Pokémon' document.getElementById('stats-gym-label').innerHTML = 'Gyms' document.getElementById('stats-pkstop-label').innerHTML = 'PokéStops' @@ -10,40 +11,82 @@ function countMarkers () { // eslint-disable-line no-unused-vars var pkmnTotal = 0 var pokestopCount = [] var pokestopTotal = 0 + var pokeStatTable = $('#pokemonList_table').DataTable() + + // Bounds of the currently visible map + var currentVisibleMap = map.getBounds() + + // Is a particular Pokémon/Gym/Pokéstop within the currently visible map? + var thisPokeIsVisible = false + var thisGymIsVisible = false + var thisPokestopIsVisible = false + if (Store.get('showPokemon')) { $.each(mapData.pokemons, function (key, value) { - if (pkmnCount[mapData.pokemons[key]['pokemon_id']] === 0 || !pkmnCount[mapData.pokemons[key]['pokemon_id']]) { - pkmnCount[mapData.pokemons[key]['pokemon_id']] = { - 'ID': mapData.pokemons[key]['pokemon_id'], - 'Count': 1, - 'Name': mapData.pokemons[key]['pokemon_name'] + var thisPokeLocation = { lat: mapData.pokemons[key]['latitude'], lng: mapData.pokemons[key]['longitude'] } + thisPokeIsVisible = currentVisibleMap.contains(thisPokeLocation) + + if (thisPokeIsVisible) { + pkmnTotal++ + if (pkmnCount[mapData.pokemons[key]['pokemon_id']] === 0 || !pkmnCount[mapData.pokemons[key]['pokemon_id']]) { + pkmnCount[mapData.pokemons[key]['pokemon_id']] = { + 'ID': mapData.pokemons[key]['pokemon_id'], + 'Count': 1, + 'Name': mapData.pokemons[key]['pokemon_name'] + } + } else { + pkmnCount[mapData.pokemons[key]['pokemon_id']].Count += 1 } - } else { - pkmnCount[mapData.pokemons[key]['pokemon_id']].Count += 1 } - pkmnTotal++ }) - pkmnCount.sort(sortBy('Name', false)) - var pkmnListString = '' + + var pokeCounts = [] + for (i = 0; i < pkmnCount.length; i++) { if (pkmnCount[i] && pkmnCount[i].Count > 0) { - pkmnListString += '' + pokeCounts.push( + [ + '', + '' + pkmnCount[i].Name + '', + pkmnCount[i].Count, + (Math.round(pkmnCount[i].Count * 100 / pkmnTotal * 10) / 10) + '%' + ] + ) } } - pkmnListString += '
IconNameCount%
Total' + pkmnTotal + '
' + pkmnCount[i].Name + '' + pkmnCount[i].Count + '' + Math.round(pkmnCount[i].Count * 100 / pkmnTotal * 10) / 10 + '%
' - document.getElementById('pokemonList').innerHTML = pkmnListString + + // Clear stale data, add fresh data, redraw + + $('#pokemonList_table').dataTable().show() + pokeStatTable + .clear() + .rows.add(pokeCounts) + .draw() } else { - document.getElementById('pokemonList').innerHTML = 'Pokémons markers are disabled' - } + pokeStatTable + .clear() + .draw() + + document.getElementById('pokeStatStatus').innerHTML = 'Pokémon markers are disabled' + $('#pokemonList_table').dataTable().hide() + } // end Pokémon processing + + // begin Gyms processing if (Store.get('showGyms')) { $.each(mapData.gyms, function (key, value) { - if (arenaCount[mapData.gyms[key]['team_id']] === 0 || !arenaCount[mapData.gyms[key]['team_id']]) { - arenaCount[mapData.gyms[key]['team_id']] = 1 - } else { - arenaCount[mapData.gyms[key]['team_id']] += 1 + var thisGymLocation = { lat: mapData.gyms[key]['latitude'], lng: mapData.gyms[key]['longitude'] } + thisGymIsVisible = currentVisibleMap.contains(thisGymLocation) + + if (thisGymIsVisible) { + arenaTotal++ + if (arenaCount[mapData.gyms[key]['team_id']] === 0 || !arenaCount[mapData.gyms[key]['team_id']]) { + arenaCount[mapData.gyms[key]['team_id']] = 1 + } else { + arenaCount[mapData.gyms[key]['team_id']] += 1 + } } - arenaTotal++ }) + var arenaListString = '' for (i = 0; i < arenaCount.length; i++) { if (arenaCount[i] > 0) { @@ -63,22 +106,28 @@ function countMarkers () { // eslint-disable-line no-unused-vars } else { document.getElementById('arenaList').innerHTML = 'Gyms markers are disabled' } + if (Store.get('showPokestops')) { $.each(mapData.pokestops, function (key, value) { - if (mapData.pokestops[key]['lure_expiration'] && mapData.pokestops[key]['lure_expiration'] > 0) { - if (pokestopCount[1] === 0 || !pokestopCount[1]) { - pokestopCount[1] = 1 - } else { - pokestopCount[1] += 1 - } - } else { - if (pokestopCount[0] === 0 || !pokestopCount[0]) { - pokestopCount[0] = 1 + var thisPokestopLocation = { lat: mapData.pokestops[key]['latitude'], lng: mapData.pokestops[key]['longitude'] } + thisPokestopIsVisible = currentVisibleMap.contains(thisPokestopLocation) + + if (thisPokestopIsVisible) { + if (mapData.pokestops[key]['lure_expiration'] && mapData.pokestops[key]['lure_expiration'] > 0) { + if (pokestopCount[1] === 0 || !pokestopCount[1]) { + pokestopCount[1] = 1 + } else { + pokestopCount[1] += 1 + } } else { - pokestopCount[0] += 1 + if (pokestopCount[0] === 0 || !pokestopCount[0]) { + pokestopCount[0] = 1 + } else { + pokestopCount[0] += 1 + } } + pokestopTotal++ } - pokestopTotal++ }) var pokestopListString = '
IconTeam ColorCount%
Total' + arenaTotal + '
' for (i = 0; i < pokestopCount.length; i++) { @@ -96,21 +145,3 @@ function countMarkers () { // eslint-disable-line no-unused-vars document.getElementById('pokestopList').innerHTML = 'PokéStops markers are disabled' } } - -var sortBy = function (field, reverse, primer) { - var key = primer - ? function (x) { - return primer(x[field]) - } - : function (x) { - return x[field] - } - - reverse = !reverse ? 1 : -1 - - return function (a, b) { - a = key(a) - b = key(b) - return reverse * ((a > b) - (b > a)) - } -} diff --git a/static/sass/layout/_stats.scss b/static/sass/layout/_stats.scss index 795da6881a..b85733e7c7 100644 --- a/static/sass/layout/_stats.scss +++ b/static/sass/layout/_stats.scss @@ -7,7 +7,9 @@ background: #F8F8F8; box-shadow: none; color: _palette(accent2, fg-bold); - height: 100%; + height: -webkit-calc(100% - 3.5em); + height: -moz-calc(100% - 3.5em); + height: calc(100% - 3.5em); max-width: 80%; overflow-y: auto; padding: 1.5em 1.5em 4em 1.5em; @@ -106,4 +108,41 @@ @include breakpoint(small) { padding: 2.5em 1.75em; } + + #arenaList > img { + height: 38px; + width: 38px; + } + + .stats-label-container { + font-weight: bold; + border-width: 1px 0px; + border-style: solid; + border-color: black; + background-color: white; + margin: .75em 0 .25em 0; + } + + .stats-label-container h1 { + margin: 0.25em; + font-size: 1.2em; + font-family: 'Muli', sans-serif; + } + + // Styling for statistics dataTable + + table.dataTable.display { + margin-bottom: 2em; + } + table.dataTable.display tbody tr td, + table.dataTable.display tbody tr td img + { + vertical-align: middle; + padding: 0px 10px; + } + table.dataTable.display tbody tr td:nth-child(3) + { + text-align: center; + } + } diff --git a/static/sass/pokemon-sprite.scss b/static/sass/pokemon-sprite.scss index 68cbd10afc..d82e1b0629 100644 --- a/static/sass/pokemon-sprite.scss +++ b/static/sass/pokemon-sprite.scss @@ -7,608 +7,10 @@ -moz-transform-origin: 0 0; height: 30px; width: 30px; -} - -.pokemon-sprite.n1 { - background-position: -0px -0px; -} - -.pokemon-sprite.n2 { - background-position: -30px -0px; -} - -.pokemon-sprite.n3 { - background-position: -60px -0px; -} - -.pokemon-sprite.n4 { - background-position: -90px -0px; -} - -.pokemon-sprite.n5 { - background-position: -120px -0px; -} - -.pokemon-sprite.n6 { - background-position: -150px -0px; -} - -.pokemon-sprite.n7 { - background-position: -180px -0px; -} - -.pokemon-sprite.n8 { - background-position: -210px -0px; -} - -.pokemon-sprite.n9 { - background-position: -240px -0px; -} - -.pokemon-sprite.n10 { - background-position: -270px -0px; -} - -.pokemon-sprite.n11 { - background-position: -300px -0px; -} - -.pokemon-sprite.n12 { - background-position: -330px -0px; -} - -.pokemon-sprite.n13 { - background-position: -0px -30px; -} - -.pokemon-sprite.n14 { - background-position: -30px -30px; -} - -.pokemon-sprite.n15 { - background-position: -60px -30px; -} - -.pokemon-sprite.n16 { - background-position: -90px -30px; -} - -.pokemon-sprite.n17 { - background-position: -120px -30px; -} - -.pokemon-sprite.n18 { - background-position: -150px -30px; -} - -.pokemon-sprite.n19 { - background-position: -180px -30px; -} - -.pokemon-sprite.n20 { - background-position: -210px -30px; -} - -.pokemon-sprite.n21 { - background-position: -240px -30px; -} - -.pokemon-sprite.n22 { - background-position: -270px -30px; -} - -.pokemon-sprite.n23 { - background-position: -300px -30px; -} - -.pokemon-sprite.n24 { - background-position: -330px -30px; -} - -.pokemon-sprite.n25 { - background-position: -0px -60px; -} - -.pokemon-sprite.n26 { - background-position: -30px -60px; -} - -.pokemon-sprite.n27 { - background-position: -60px -60px; -} - -.pokemon-sprite.n28 { - background-position: -90px -60px; -} - -.pokemon-sprite.n29 { - background-position: -120px -60px; -} - -.pokemon-sprite.n30 { - background-position: -150px -60px; -} - -.pokemon-sprite.n31 { - background-position: -180px -60px; -} - -.pokemon-sprite.n32 { - background-position: -210px -60px; -} - -.pokemon-sprite.n33 { - background-position: -240px -60px; -} - -.pokemon-sprite.n34 { - background-position: -270px -60px; -} - -.pokemon-sprite.n35 { - background-position: -300px -60px; -} - -.pokemon-sprite.n36 { - background-position: -330px -60px; -} - -.pokemon-sprite.n37 { - background-position: -0px -90px; -} - -.pokemon-sprite.n38 { - background-position: -30px -90px; -} - -.pokemon-sprite.n39 { - background-position: -60px -90px; -} - -.pokemon-sprite.n40 { - background-position: -90px -90px; -} - -.pokemon-sprite.n41 { - background-position: -120px -90px; -} - -.pokemon-sprite.n42 { - background-position: -150px -90px; -} - -.pokemon-sprite.n43 { - background-position: -180px -90px; -} - -.pokemon-sprite.n44 { - background-position: -210px -90px; -} - -.pokemon-sprite.n45 { - background-position: -240px -90px; -} - -.pokemon-sprite.n46 { - background-position: -270px -90px; -} - -.pokemon-sprite.n47 { - background-position: -300px -90px; -} - -.pokemon-sprite.n48 { - background-position: -330px -90px; -} - -.pokemon-sprite.n49 { - background-position: -0px -120px; -} - -.pokemon-sprite.n50 { - background-position: -30px -120px; -} - -.pokemon-sprite.n51 { - background-position: -60px -120px; -} - -.pokemon-sprite.n52 { - background-position: -90px -120px; -} - -.pokemon-sprite.n53 { - background-position: -120px -120px; -} - -.pokemon-sprite.n54 { - background-position: -150px -120px; -} - -.pokemon-sprite.n55 { - background-position: -180px -120px; -} - -.pokemon-sprite.n56 { - background-position: -210px -120px; -} - -.pokemon-sprite.n57 { - background-position: -240px -120px; -} - -.pokemon-sprite.n58 { - background-position: -270px -120px; -} - -.pokemon-sprite.n59 { - background-position: -300px -120px; -} - -.pokemon-sprite.n60 { - background-position: -330px -120px; -} - -.pokemon-sprite.n61 { - background-position: -0px -150px; -} - -.pokemon-sprite.n62 { - background-position: -30px -150px; -} - -.pokemon-sprite.n63 { - background-position: -60px -150px; -} - -.pokemon-sprite.n64 { - background-position: -90px -150px; -} - -.pokemon-sprite.n65 { - background-position: -120px -150px; -} - -.pokemon-sprite.n66 { - background-position: -150px -150px; -} - -.pokemon-sprite.n67 { - background-position: -180px -150px; -} - -.pokemon-sprite.n68 { - background-position: -210px -150px; -} - -.pokemon-sprite.n69 { - background-position: -240px -150px; -} - -.pokemon-sprite.n70 { - background-position: -270px -150px; -} - -.pokemon-sprite.n71 { - background-position: -300px -150px; -} - -.pokemon-sprite.n72 { - background-position: -330px -150px; -} - -.pokemon-sprite.n73 { - background-position: -0px -180px; -} - -.pokemon-sprite.n74 { - background-position: -30px -180px; -} - -.pokemon-sprite.n75 { - background-position: -60px -180px; -} - -.pokemon-sprite.n76 { - background-position: -90px -180px; -} - -.pokemon-sprite.n77 { - background-position: -120px -180px; -} - -.pokemon-sprite.n78 { - background-position: -150px -180px; -} - -.pokemon-sprite.n79 { - background-position: -180px -180px; -} - -.pokemon-sprite.n80 { - background-position: -210px -180px; -} - -.pokemon-sprite.n81 { - background-position: -240px -180px; -} - -.pokemon-sprite.n82 { - background-position: -270px -180px; -} - -.pokemon-sprite.n83 { - background-position: -300px -180px; -} - -.pokemon-sprite.n84 { - background-position: -330px -180px; -} - -.pokemon-sprite.n85 { - background-position: -0px -210px; -} - -.pokemon-sprite.n86 { - background-position: -30px -210px; -} - -.pokemon-sprite.n87 { - background-position: -60px -210px; -} - -.pokemon-sprite.n88 { - background-position: -90px -210px; -} - -.pokemon-sprite.n89 { - background-position: -120px -210px; -} - -.pokemon-sprite.n90 { - background-position: -150px -210px; -} - -.pokemon-sprite.n91 { - background-position: -180px -210px; -} - -.pokemon-sprite.n92 { - background-position: -210px -210px; -} - -.pokemon-sprite.n93 { - background-position: -240px -210px; -} - -.pokemon-sprite.n94 { - background-position: -270px -210px; -} - -.pokemon-sprite.n95 { - background-position: -300px -210px; -} - -.pokemon-sprite.n96 { - background-position: -330px -210px; -} - -.pokemon-sprite.n97 { - background-position: -0px -240px; -} - -.pokemon-sprite.n98 { - background-position: -30px -240px; -} - -.pokemon-sprite.n99 { - background-position: -60px -240px; -} - -.pokemon-sprite.n100 { - background-position: -90px -240px; -} - -.pokemon-sprite.n101 { - background-position: -120px -240px; -} - -.pokemon-sprite.n102 { - background-position: -150px -240px; -} - -.pokemon-sprite.n103 { - background-position: -180px -240px; -} - -.pokemon-sprite.n104 { - background-position: -210px -240px; -} - -.pokemon-sprite.n105 { - background-position: -240px -240px; -} - -.pokemon-sprite.n106 { - background-position: -270px -240px; -} - -.pokemon-sprite.n107 { - background-position: -300px -240px; -} - -.pokemon-sprite.n108 { - background-position: -330px -240px; -} - -.pokemon-sprite.n109 { - background-position: -0px -270px; -} - -.pokemon-sprite.n110 { - background-position: -30px -270px; -} - -.pokemon-sprite.n111 { - background-position: -60px -270px; -} - -.pokemon-sprite.n112 { - background-position: -90px -270px; -} - -.pokemon-sprite.n113 { - background-position: -120px -270px; -} - -.pokemon-sprite.n114 { - background-position: -150px -270px; -} - -.pokemon-sprite.n115 { - background-position: -180px -270px; -} - -.pokemon-sprite.n116 { - background-position: -210px -270px; -} - -.pokemon-sprite.n117 { - background-position: -240px -270px; -} - -.pokemon-sprite.n118 { - background-position: -270px -270px; -} - -.pokemon-sprite.n119 { - background-position: -300px -270px; -} - -.pokemon-sprite.n120 { - background-position: -330px -270px; -} - -.pokemon-sprite.n121 { - background-position: -0px -300px; -} - -.pokemon-sprite.n122 { - background-position: -30px -300px; -} - -.pokemon-sprite.n123 { - background-position: -60px -300px; -} - -.pokemon-sprite.n124 { - background-position: -90px -300px; -} - -.pokemon-sprite.n125 { - background-position: -120px -300px; -} - -.pokemon-sprite.n126 { - background-position: -150px -300px; -} - -.pokemon-sprite.n127 { - background-position: -180px -300px; -} - -.pokemon-sprite.n128 { - background-position: -210px -300px; -} - -.pokemon-sprite.n129 { - background-position: -240px -300px; -} - -.pokemon-sprite.n130 { - background-position: -270px -300px; -} - -.pokemon-sprite.n131 { - background-position: -300px -300px; -} - -.pokemon-sprite.n132 { - background-position: -330px -300px; -} - -.pokemon-sprite.n133 { - background-position: -0px -330px; -} - -.pokemon-sprite.n134 { - background-position: -30px -330px; -} - -.pokemon-sprite.n135 { - background-position: -60px -330px; -} - -.pokemon-sprite.n136 { - background-position: -90px -330px; -} - -.pokemon-sprite.n137 { - background-position: -120px -330px; -} - -.pokemon-sprite.n138 { - background-position: -150px -330px; -} - -.pokemon-sprite.n139 { - background-position: -180px -330px; -} - -.pokemon-sprite.n140 { - background-position: -210px -330px; -} - -.pokemon-sprite.n141 { - background-position: -240px -330px; -} - -.pokemon-sprite.n142 { - background-position: -270px -330px; -} - -.pokemon-sprite.n143 { - background-position: -300px -330px; -} - -.pokemon-sprite.n144 { - background-position: -330px -330px; -} - -.pokemon-sprite.n145 { - background-position: -0px -360px; -} - -.pokemon-sprite.n146 { - background-position: -30px -360px; -} - -.pokemon-sprite.n147 { - background-position: -60px -360px; -} - -.pokemon-sprite.n148 { - background-position: -90px -360px; -} - -.pokemon-sprite.n149 { - background-position: -120px -360px; -} - -.pokemon-sprite.n150 { - background-position: -150px -360px; -} -.pokemon-sprite.n151 { - background-position: -180px -360px; + @for $i from 1 through 151 { + &.n#{$i} { + background-position: -#{(($i - 1) % 12) * 30}px -#{floor(($i - 1) / 12) * 30}px + } + } } diff --git a/templates/map.html b/templates/map.html index 7e58c9f408..16811b5aee 100644 --- a/templates/map.html +++ b/templates/map.html @@ -30,6 +30,7 @@ + @@ -121,7 +122,7 @@

Hide Pokémon

-

Location & Search Settings

+

Location & Search Settings

+
+ +

Notify with sound

@@ -235,6 +242,11 @@

Search Icon Marker

+

Gym Marker Style

+

Location Icon Marker

@@ -245,14 +257,29 @@

Location Icon Marker

-

Loading...

-
+

Loading...

+
+
+

+
+
+
IconStatusCount%
Total' + pokestopTotal + '
+ + + + + + +
Icon Name Count %
+
+
+

-
+

@@ -266,6 +293,7 @@

Location Icon Marker

+