├── .babelrc ├── .gitattributes ├── .gitignore ├── .greet ├── .help ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── api ├── cfg.js.example ├── css │ ├── main.css │ ├── pure.min.css │ └── vex.css ├── img │ ├── gym_NEUTRAL.png │ ├── gym_blue.png │ ├── gym_red.png │ ├── gym_yellow.png │ ├── license.txt │ ├── pokestop_blue.png │ ├── pokestop_lure.png │ ├── pokestop_puple.png │ └── spawn_point.png ├── index.html ├── index.js └── js │ ├── ajax.js │ ├── gmaps.js │ ├── init.js │ └── main.js ├── cfg.js.example ├── install-windows.bat ├── package.json ├── src ├── api.js ├── commands.js ├── cycle.js ├── db │ ├── create.js │ ├── get.js │ ├── index.js │ ├── query.js │ └── tables │ │ ├── gym.table │ │ ├── owned_pkmn.table │ │ ├── pokestop.table │ │ ├── spawn_points.table │ │ └── users.table ├── dump.js ├── enum.js ├── http.js ├── index.js ├── models │ ├── GameMaster │ │ └── index.js │ ├── Player │ │ ├── Avatar │ │ │ └── index.js │ │ ├── Bag │ │ │ └── index.js │ │ ├── CandyBag │ │ │ └── index.js │ │ ├── Contact │ │ │ └── index.js │ │ ├── Currency │ │ │ └── index.js │ │ ├── Info │ │ │ └── index.js │ │ ├── Party │ │ │ └── index.js │ │ ├── PokeDex │ │ │ └── index.js │ │ ├── Tutorial │ │ │ └── index.js │ │ ├── index.js │ │ └── packets │ │ │ ├── CheckAwardedBadges.js │ │ │ ├── ClaimCodename.js │ │ │ ├── GetAssetDigest.js │ │ │ ├── GetAuthTicket.js │ │ │ ├── GetHatchedEggs.js │ │ │ ├── GetInventory.js │ │ │ ├── GetPlayer.js │ │ │ ├── GetPlayerProfile.js │ │ │ ├── LevelUpRewards.js │ │ │ ├── NicknamePokemon.js │ │ │ ├── RecycleInventoryItem.js │ │ │ ├── ReleasePokemon.js │ │ │ ├── SetAvatar.js │ │ │ ├── SetFavoritePokemon.js │ │ │ ├── UpgradePokemon.js │ │ │ └── index.js │ ├── Pokemon │ │ ├── WildPokemon │ │ │ └── index.js │ │ ├── action.js │ │ ├── calc.js │ │ └── index.js │ └── World │ │ ├── Cell │ │ └── index.js │ │ ├── Fort │ │ ├── Gym │ │ │ └── index.js │ │ ├── Pokestop │ │ │ └── index.js │ │ └── index.js │ │ ├── MapObject │ │ └── index.js │ │ ├── SpawnPoint │ │ └── index.js │ │ ├── forts.js │ │ ├── index.js │ │ ├── packets │ │ ├── CatchPokemon.js │ │ ├── CheckChallenge.js │ │ ├── DownloadItemTemplates.js │ │ ├── DownloadRemoteConfigVersion.js │ │ ├── DownloadSettings.js │ │ ├── Encounter.js │ │ ├── FortDetails.js │ │ ├── FortSearch.js │ │ ├── GetDownloadUrls.js │ │ ├── GetMapObjects.js │ │ └── index.js │ │ └── players.js ├── modes │ └── index.js ├── print.js ├── process.js ├── request.js ├── response.js ├── setup.js ├── shared.js └── utils.js ├── supervisord.conf └── updater.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Line endings: enforce LF in GitHub, convert to native on checkout. 2 | 3 | * text=auto 4 | *.js text 5 | 6 | # Make GitHub ignore vendor libraries while computing language stats. 7 | # See https://github.com/github/linguist#overrides. 8 | 9 | *.proto linguist-vendored=true 10 | *.sh linguist-vendored=true 11 | *.bat linguist-vendored=true 12 | *.html linguist-vendored=true 13 | *.css linguist-vendored=true 14 | 15 | api/* linguist-vendored=true 16 | 17 | # Explicitly specify language for non-standard extensions used under 18 | # ide/web/lib/templates to make GitHub correctly count their language stats. 19 | # 20 | *.js_ linguist-language=JavaScript 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | scripts/ 3 | data/ 4 | logs/ 5 | cfg.js 6 | .save 7 | npm-debug.* 8 | -------------------------------------------------------------------------------- /.greet: -------------------------------------------------------------------------------- 1 | ______ _____ _____ _____ 2 | | ___ \ _ | __ \ _ | 3 | | |_/ / | | | | \/ | | | ___ ___ _ ____ _____ _ __ 4 | | __/| | | | | __| | | |/ __|/ _ \ '__\ \ / / _ \ '__| 5 | | | \ \_/ / |_\ \ \_/ /\__ \ __/ | \ V / __/ | 6 | \_| \___/ \____/\___/ |___/\___|_| \_/ \___|_| 7 | -------------------------------------------------------------------------------- /.help: -------------------------------------------------------------------------------- 1 | players : How many players are connected 2 | exit : Exit the server 3 | kick [Username] : Kick player by username 4 | kickall : Kick all players 5 | clear : Clear the server console 6 | save : Save all players into database 7 | spawn [Username] [Pkmn] [Amount] : Spawn pokemons at users position 8 | dump : Dump assets -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | script: "npm run test" 4 | node_js: 5 | - "4" 6 | - "5" 7 | - "6" -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | MAINTAINER Draco Miles X 3 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -qq mysql-server mysql-client -y && apt-get upgrade -y && apt-get dist-upgrade -y 4 | RUN apt-get install -y \ 5 | apt-utils \ 6 | nano \ 7 | build-essential \ 8 | curl \ 9 | lsb-release \ 10 | openssl \ 11 | libssl-dev \ 12 | openssh-server \ 13 | openssh-client \ 14 | sudo \ 15 | python \ 16 | python-dev \ 17 | python-pip \ 18 | python3 \ 19 | python3-dev \ 20 | python3-pip \ 21 | pkg-config \ 22 | autoconf \ 23 | automake \ 24 | libtool \ 25 | curl \ 26 | make \ 27 | g++ \ 28 | unzip \ 29 | supervisor 30 | RUN mkdir -p /var/run/sshd /var/log/supervisor 31 | RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 32 | RUN apt-get install -y \ 33 | nodejs \ 34 | git 35 | RUN git clone --recursive https://github.com/google/protobuf.git 36 | RUN cd /protobuf && ./autogen.sh && ./configure && make && make check && make install && ldconfig 37 | RUN cd / 38 | RUN git clone --recursive https://github.com/maierfelix/POGOserver.git 39 | COPY cfg.js /POGOserver/ 40 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 41 | RUN service mysql start && mysql -u root -e "create database pogosql"; 42 | EXPOSE 22 80 443 3306 3000 43 | CMD ["/usr/bin/supervisord"] 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```` 2 | ______ _____ _____ _____ 3 | | ___ \ _ | __ \ _ | 4 | | |_/ / | | | | \/ | | | ___ ___ _ ____ _____ _ __ 5 | | __/| | | | | __| | | |/ __|/ _ \ '__\ \ / / _ \ '__| 6 | | | \ \_/ / |_\ \ \_/ /\__ \ __/ | \ V / __/ | 7 | \_| \___/ \____/\___/ |___/\___|_| \_/ \___|_| 8 | ```` 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Stability 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | # Getting started 27 | 28 | ## Setup 29 | 30 | Copy and rename ``cfg.js.example`` to ``cfg.js``. 31 | 32 | Open ``cfg.js`` and fill the following fields: 33 | 34 | ````js 35 | DOWNLOAD_PROVIDER: "GOOGLE"; 36 | DOWNLOAD_USERNAME: "USERNAME"; 37 | DOWNLOAD_PASSWORD: "PASSWORD"; 38 | ```` 39 | 40 | ## Tunneling setup 41 | The pokemon go app traffic has to get forwarded manually to this custom server. Download [rastapasta](https://github.com/rastapasta)'s [Pokemon Go Xposed](https://github.com/rastapasta/pokemon-go-xposed/releases) app and follow the installation instructions [here](https://github.com/rastapasta/pokemon-go-xposed#how-to-use-it). 42 | 43 | ## Database setup 44 | 45 | To setup a database connection, open ``cfg.js`` and change the database login credentials: 46 | 47 | ````js 48 | MYSQL_PORT: 3306, 49 | MYSQL_HOST_IP: "127.0.0.1", 50 | MYSQL_DB_NAME: "pogosql", 51 | MYSQL_USERNAME: "root", 52 | MYSQL_PASSWORD: "", 53 | ```` 54 | 55 | The required database tables get generated automatically. 56 | 57 | ## Server setup 58 | 59 | You need at minimum [Node.js](https://nodejs.org/en/) version 6.x. 60 | 61 | Open up a terminal and enter ``npm run boot`` to start the server or ``npm run api`` to start the web-api. 62 | 63 | To Update the Server enter ``npm run update`` 64 | 65 | ## Docker setup 66 | 67 | 1. Download ``Dockerfile``, ``cfg.js.example`` and ``supervisord.conf`` from github. 68 | 2. Place ``Dockerfile``, ``cfg.js.example`` and ``supervisord.conf`` into the same folder. Rename ``cfg.js.example`` to ``cfg.js``. 69 | 3. Modify ``cfg.js`` to your requirements as described above. 70 | 4. Create a container and run it. 71 | 5. Open a bash prompt, enter: ``cd /POGOserver/`` and enter ``npm run boot``. 72 | 6. Connect the Pokemon Go app to the server. 73 | 7. Done. 74 | 75 | Note: Instead of automatically mapping the ports, map them static, so they don't change after reboot. 76 | -------------------------------------------------------------------------------- /api/cfg.js.example: -------------------------------------------------------------------------------- 1 | const CFG = { 2 | API: { 3 | HOST: "127.0.0.1", 4 | PORT: 3000, 5 | ROUTE: "/api" 6 | }, 7 | GMAPS: { 8 | API_KEY: "XXXXX", 9 | BASE_ZOOM: 20, 10 | BASE_LAT: 39.18875480450959, 11 | BASE_LNG: -96.58109955489635 12 | } 13 | }; -------------------------------------------------------------------------------- /api/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-animation: fadein 1s; 3 | -moz-animation: fadein 1s; 4 | -ms-animation: fadein 1s; 5 | -o-animation: fadein 1s; 6 | animation: fadein 1s; 7 | } 8 | 9 | body { 10 | overflow-x: hidden; 11 | overflow-y: scroll !important; 12 | } 13 | 14 | #map { 15 | position: absolute; 16 | left: calc(50% - 400px); 17 | width: 800px !important; 18 | height: 400px !important; 19 | } 20 | 21 | #map_manager { 22 | padding-bottom: 500px; 23 | margin-top: -100px; 24 | display: none; 25 | } 26 | 27 | .view { 28 | letter-spacing: 0px; 29 | padding: 5px 25px !important; 30 | margin-top: -35px; 31 | } 32 | 33 | .area { 34 | font-family: "Andale Mono", AndaleMono, monospace; 35 | text-align: center; 36 | font-style: normal; 37 | font-variant: normal; 38 | -webkit-font-smoothing: antialiased; 39 | font-size: 75px; 40 | letter-spacing: -5px; 41 | color: #fff; 42 | position: relative; 43 | margin-top: 5px; 44 | text-transform: uppercase; 45 | } 46 | 47 | .Codemirror { 48 | text-align: left; 49 | } 50 | 51 | .ping { 52 | position: absolute; 53 | top: 0px; 54 | left: 10px; 55 | font-size: 18px; 56 | letter-spacing: 0px; 57 | } 58 | 59 | .version { 60 | font-size: 15px; 61 | letter-spacing: 0px; 62 | margin-top: -75px; 63 | margin-bottom: -25px; 64 | text-transform: none; 65 | } 66 | 67 | .btn { 68 | text-transform: uppercase; 69 | } 70 | 71 | .centered { 72 | text-align: center; 73 | position: relative; 74 | top: 40%; 75 | transform: translateY(-40%); 76 | margin-top: -175px; 77 | -webkit-transform: translateY(-40%); 78 | -moz-transform: translateY(0%); 79 | } 80 | 81 | @-moz-document url-prefix() { 82 | .centered { 83 | margin-top: 0px; 84 | } 85 | } 86 | 87 | .body { 88 | background: #2d2d2d; 89 | background-color: #2d2d2d; 90 | } 91 | 92 | .star { 93 | position: absolute; 94 | width: 2px; 95 | height: 2px; 96 | background: rgba(255, 255, 255, 0.45); 97 | opacity: 1; 98 | } 99 | 100 | .btn { 101 | font-family: "Andale Mono", AndaleMono, monospace; 102 | display: inline-block; 103 | padding: 5px; 104 | border: 1px solid rgba(255, 255, 255, .35); 105 | border-radius: 4px; 106 | color: rgba(255, 255, 255, .75); 107 | text-decoration: none; 108 | transition: border .35s, background .35s; 109 | min-width: 100px; 110 | text-align: center; 111 | font-size: 15px; 112 | font-style: normal; 113 | font-variant: normal; 114 | font-weight: 500; 115 | line-height: 25px; 116 | cursor: pointer; 117 | } 118 | 119 | .input { 120 | background: rgba(0,0,0,0); 121 | cursor: auto; 122 | } 123 | 124 | input:focus { 125 | outline: none; 126 | } 127 | 128 | button:focus { 129 | outline: none; 130 | } 131 | 132 | .label { 133 | background: rgba(255, 255, 255, .05); 134 | border: 1px solid rgba(255, 255, 255, .5); 135 | color: #79a2b7; 136 | } 137 | 138 | .info { 139 | padding-bottom: 45px; 140 | margin-top: -45px; 141 | font-size: 18px; 142 | letter-spacing: 0px; 143 | } 144 | 145 | .cmd_label { 146 | border: none; 147 | background: rgba(255,255,255,0.05); 148 | color: white; 149 | cursor: auto; 150 | text-transform: none; 151 | } 152 | 153 | .submit { 154 | background: rgba(165, 165, 165, 0.15); 155 | } 156 | 157 | .with_label { 158 | margin-left: 25px; 159 | width: 120px; 160 | } 161 | 162 | #connection_status { 163 | text-transform: uppercase; 164 | } 165 | 166 | .login_area { 167 | font-size: 25px; 168 | letter-spacing: 0px; 169 | text-transform: none; 170 | margin: -25px; 171 | padding: 0px; 172 | } 173 | 174 | .login_input { 175 | text-transform: none; 176 | background: rgba(0,0,0,0); 177 | } 178 | 179 | .noselect { 180 | -webkit-touch-callout: none; 181 | -webkit-user-select: none; 182 | -khtml-user-select: none; 183 | -moz-user-select: none; 184 | -ms-user-select: none; 185 | user-select: none; 186 | } 187 | 188 | .btn:hover { 189 | background: rgba(255, 255, 255, .05); 190 | border: 1px solid rgba(255, 255, 255, .5); 191 | } 192 | 193 | .txt { 194 | margin-left: calc(20%); 195 | margin-right: calc(20%); 196 | left: auto; 197 | width: auto; 198 | min-height: 300px; 199 | background: rgba(255, 255, 255, 0.0) !important; 200 | border: 1px solid rgba(255, 255, 255, 0.0) !important; 201 | border-radius: 4px !important; 202 | padding: 10px 15px !important; 203 | color: rgba(255, 255, 255, .75) !important; 204 | resize: none; 205 | transition: color .35s !important; 206 | } 207 | 208 | .txt:focus { 209 | outline: none; 210 | color: rgba(255, 255, 255, .85); 211 | } 212 | 213 | ::-moz-selection { 214 | color: rgba(255, 255, 255, .85); 215 | background: rgba(255, 255, 255, .075); 216 | } 217 | ::selection { 218 | color: rgba(255, 255, 255, .85); 219 | background: rgba(255, 255, 255, .075); 220 | } 221 | 222 | ::-webkit-scrollbar{ 223 | width: 10px; 224 | height: 0px; 225 | background: transparent; 226 | } 227 | ::-webkit-scrollbar-thumb{ 228 | background: rgba(255, 255, 255, .15); 229 | border-radius: 5px; 230 | } 231 | ::-webkit-scrollbar-corner{ 232 | background: transparent; 233 | } 234 | 235 | @keyframes fadein { 236 | from { opacity: 0; } 237 | to { opacity: 1; } 238 | } 239 | @-webkit-keyframes fadein { 240 | from { opacity: 0; } 241 | to { opacity: 1; } 242 | } 243 | @-moz-keyframes fadein { 244 | from { opacity: 0; } 245 | to { opacity: 1; } 246 | } 247 | 248 | @keyframes pulsate { 249 | 0% {transform: scale(0.1, 0.1); opacity: 0.0;} 250 | 50% {opacity: 1.0;} 251 | 100% {transform: scale(1.2, 1.2); opacity: 0.0;} 252 | } 253 | @-webkit-keyframes pulsate { 254 | 0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;} 255 | 50% {opacity: 1.0;} 256 | 100% {-webkit-transform: scale(1.2, 1.2); opacity: 0.0;} 257 | } 258 | @-moz-keyframes pulsate { 259 | 0% {transform: scale(0.1, 0.1); opacity: 0.0;} 260 | 50% {opacity: 1.0;} 261 | 100% {transform: scale(1.2, 1.2); opacity: 0.0;} 262 | } -------------------------------------------------------------------------------- /api/css/pure.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}/*! 12 | Pure v0.6.0 13 | Copyright 2014 Yahoo! Inc. All rights reserved. 14 | Licensed under the BSD License. 15 | https://github.com/yahoo/pure/blob/master/LICENSE.md 16 | */ 17 | .pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}/*! 18 | Pure v0.6.0 19 | Copyright 2014 Yahoo! Inc. All rights reserved. 20 | Licensed under the BSD License. 21 | https://github.com/yahoo/pure/blob/master/LICENSE.md 22 | */ 23 | @media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} -------------------------------------------------------------------------------- /api/css/vex.css: -------------------------------------------------------------------------------- 1 | @keyframes vex-fadein { 2 | 0% { 3 | opacity: 0; } 4 | 100% { 5 | opacity: 1; } } 6 | 7 | @-webkit-keyframes vex-fadein { 8 | 0% { 9 | opacity: 0; } 10 | 100% { 11 | opacity: 1; } } 12 | 13 | @-moz-keyframes vex-fadein { 14 | 0% { 15 | opacity: 0; } 16 | 100% { 17 | opacity: 1; } } 18 | 19 | @-ms-keyframes vex-fadein { 20 | 0% { 21 | opacity: 0; } 22 | 100% { 23 | opacity: 1; } } 24 | 25 | @-o-keyframes vex-fadein { 26 | 0% { 27 | opacity: 0; } 28 | 100% { 29 | opacity: 1; } } 30 | 31 | @keyframes vex-fadeout { 32 | 0% { 33 | opacity: 1; } 34 | 100% { 35 | opacity: 0; } } 36 | 37 | @-webkit-keyframes vex-fadeout { 38 | 0% { 39 | opacity: 1; } 40 | 100% { 41 | opacity: 0; } } 42 | 43 | @-moz-keyframes vex-fadeout { 44 | 0% { 45 | opacity: 1; } 46 | 100% { 47 | opacity: 0; } } 48 | 49 | @-ms-keyframes vex-fadeout { 50 | 0% { 51 | opacity: 1; } 52 | 100% { 53 | opacity: 0; } } 54 | 55 | @-o-keyframes vex-fadeout { 56 | 0% { 57 | opacity: 1; } 58 | 100% { 59 | opacity: 0; } } 60 | 61 | @keyframes vex-rotation { 62 | 0% { 63 | transform: rotate(0deg); 64 | -webkit-transform: rotate(0deg); 65 | -moz-transform: rotate(0deg); 66 | -ms-transform: rotate(0deg); 67 | -o-transform: rotate(0deg); } 68 | 100% { 69 | transform: rotate(359deg); 70 | -webkit-transform: rotate(359deg); 71 | -moz-transform: rotate(359deg); 72 | -ms-transform: rotate(359deg); 73 | -o-transform: rotate(359deg); } } 74 | 75 | @-webkit-keyframes vex-rotation { 76 | 0% { 77 | transform: rotate(0deg); 78 | -webkit-transform: rotate(0deg); 79 | -moz-transform: rotate(0deg); 80 | -ms-transform: rotate(0deg); 81 | -o-transform: rotate(0deg); } 82 | 100% { 83 | transform: rotate(359deg); 84 | -webkit-transform: rotate(359deg); 85 | -moz-transform: rotate(359deg); 86 | -ms-transform: rotate(359deg); 87 | -o-transform: rotate(359deg); } } 88 | 89 | @-moz-keyframes vex-rotation { 90 | 0% { 91 | transform: rotate(0deg); 92 | -webkit-transform: rotate(0deg); 93 | -moz-transform: rotate(0deg); 94 | -ms-transform: rotate(0deg); 95 | -o-transform: rotate(0deg); } 96 | 100% { 97 | transform: rotate(359deg); 98 | -webkit-transform: rotate(359deg); 99 | -moz-transform: rotate(359deg); 100 | -ms-transform: rotate(359deg); 101 | -o-transform: rotate(359deg); } } 102 | 103 | @-ms-keyframes vex-rotation { 104 | 0% { 105 | transform: rotate(0deg); 106 | -webkit-transform: rotate(0deg); 107 | -moz-transform: rotate(0deg); 108 | -ms-transform: rotate(0deg); 109 | -o-transform: rotate(0deg); } 110 | 100% { 111 | transform: rotate(359deg); 112 | -webkit-transform: rotate(359deg); 113 | -moz-transform: rotate(359deg); 114 | -ms-transform: rotate(359deg); 115 | -o-transform: rotate(359deg); } } 116 | 117 | @-o-keyframes vex-rotation { 118 | 0% { 119 | transform: rotate(0deg); 120 | -webkit-transform: rotate(0deg); 121 | -moz-transform: rotate(0deg); 122 | -ms-transform: rotate(0deg); 123 | -o-transform: rotate(0deg); } 124 | 100% { 125 | transform: rotate(359deg); 126 | -webkit-transform: rotate(359deg); 127 | -moz-transform: rotate(359deg); 128 | -ms-transform: rotate(359deg); 129 | -o-transform: rotate(359deg); } } 130 | 131 | .vex, .vex *, .vex *:before, .vex *:after { 132 | -moz-box-sizing: border-box; 133 | -webkit-box-sizing: border-box; 134 | box-sizing: border-box; } 135 | 136 | .vex { 137 | position: fixed; 138 | overflow: auto; 139 | -webkit-overflow-scrolling: touch; 140 | z-index: 1111; 141 | top: 0; 142 | right: 0; 143 | bottom: 0; 144 | left: 0; } 145 | 146 | .vex-scrollbar-measure { 147 | position: absolute; 148 | top: -9999px; 149 | width: 50px; 150 | height: 50px; 151 | overflow: scroll; } 152 | 153 | .vex-overlay { 154 | background: #000; 155 | filter: alpha(opacity=40); 156 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; } 157 | 158 | .vex-overlay { 159 | animation: vex-fadein 0.5s; 160 | -webkit-animation: vex-fadein 0.5s; 161 | -moz-animation: vex-fadein 0.5s; 162 | -ms-animation: vex-fadein 0.5s; 163 | -o-animation: vex-fadein 0.5s; 164 | -webkit-backface-visibility: hidden; 165 | position: fixed; 166 | background: rgba(0, 0, 0, 0.4); 167 | top: 0; 168 | right: 0; 169 | bottom: 0; 170 | left: 0; } 171 | 172 | .vex-close:before { 173 | font-family: Arial, sans-serif; 174 | content: "\00D7"; } 175 | 176 | .vex-dialog-form { 177 | margin: 0; } 178 | 179 | .vex-dialog-button { 180 | text-rendering: optimizeLegibility; 181 | -moz-appearance: none; 182 | -webkit-appearance: none; 183 | cursor: pointer; 184 | -webkit-tap-highlight-color: transparent; } 185 | 186 | .vex-loading-spinner { 187 | animation: vex-rotation 0.7s linear infinite; 188 | -webkit-animation: vex-rotation 0.7s linear infinite; 189 | -moz-animation: vex-rotation 0.7s linear infinite; 190 | -ms-animation: vex-rotation 0.7s linear infinite; 191 | -o-animation: vex-rotation 0.7s linear infinite; 192 | -webkit-backface-visibility: hidden; 193 | -moz-box-shadow: 0 0 1em rgba(0, 0, 0, 0.1); 194 | -webkit-box-shadow: 0 0 1em rgba(0, 0, 0, 0.1); 195 | box-shadow: 0 0 1em rgba(0, 0, 0, 0.1); 196 | position: fixed; 197 | z-index: 1112; 198 | margin: auto; 199 | top: 0; 200 | right: 0; 201 | bottom: 0; 202 | left: 0; 203 | height: 2em; 204 | width: 2em; 205 | background: #fff; } 206 | 207 | body.vex-open { 208 | overflow: hidden; } 209 | -------------------------------------------------------------------------------- /api/img/gym_NEUTRAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/gym_NEUTRAL.png -------------------------------------------------------------------------------- /api/img/gym_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/gym_blue.png -------------------------------------------------------------------------------- /api/img/gym_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/gym_red.png -------------------------------------------------------------------------------- /api/img/gym_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/gym_yellow.png -------------------------------------------------------------------------------- /api/img/license.txt: -------------------------------------------------------------------------------- 1 | https://shareicon.net/license/cc-3-0-by -------------------------------------------------------------------------------- /api/img/pokestop_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/pokestop_blue.png -------------------------------------------------------------------------------- /api/img/pokestop_lure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/pokestop_lure.png -------------------------------------------------------------------------------- /api/img/pokestop_puple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/pokestop_puple.png -------------------------------------------------------------------------------- /api/img/spawn_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maierfelix/POGOserver/0f73b8805e9dd5e9a1ec309fa1e1f07361689943/api/img/spawn_point.png -------------------------------------------------------------------------------- /api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | POGOserver api 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |

POGOserver

24 |

 

25 |

26 |
27 | 28 |
29 |

Login

30 |

31 |

32 |

33 |
34 | 35 | 42 | 43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | import url from "url"; 3 | import path from "path"; 4 | import fs from "fs"; 5 | const port = process.argv[2] || 9000; 6 | 7 | http.createServer((request, response) => { 8 | const uri = url.parse(request.url).pathname; 9 | let filename = path.join(process.cwd(), uri); 10 | 11 | fs.exists(filename, exists => { 12 | if(!exists) { 13 | response.writeHead(404, {"Content-Type": "text/plain"}); 14 | response.write("404 Not Found\n"); 15 | response.end(); 16 | return; 17 | } 18 | 19 | if (fs.statSync(filename).isDirectory()) filename += '/api/index.html'; 20 | 21 | fs.readFile(filename, "binary", (err, file) => { 22 | if(err) { 23 | response.writeHead(500, {"Content-Type": "text/plain"}); 24 | response.write(`${err}\n`); 25 | response.end(); 26 | return; 27 | } 28 | 29 | response.writeHead(200); 30 | response.write(file, "binary"); 31 | response.end(); 32 | }); 33 | }); 34 | }).listen(parseInt(port, 10)); 35 | 36 | console.log(`Web-API for POGOserver running at => http://localhost:${port}/\nCTRL + C to shutdown`); -------------------------------------------------------------------------------- /api/js/ajax.js: -------------------------------------------------------------------------------- 1 | function send(data, resolve) { 2 | const xhr = new XMLHttpRequest(); 3 | const protocol = window.location.protocol; 4 | xhr.open("POST", `${protocol}//${CFG.API.HOST}:${CFG.API.PORT}${CFG.API.ROUTE}`, true); 5 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 6 | xhr.onreadystatechange = () => { 7 | if (xhr.readyState === 4) { 8 | if (xhr.status === 200) { 9 | if (typeof resolve === "function") { 10 | try { 11 | resolve(JSON.parse(xhr.responseText)); 12 | } catch (e) { 13 | resolve(void 0); 14 | } 15 | } 16 | } else { 17 | resolve(xhr.statusText); 18 | } 19 | } 20 | }; 21 | xhr.send(JSON.stringify(data)); 22 | } -------------------------------------------------------------------------------- /api/js/init.js: -------------------------------------------------------------------------------- 1 | ((() => { 2 | 3 | function loadScriptDefered(src) { 4 | let js = null; 5 | js = document.createElement("script"); 6 | js.type = "text/javascript"; 7 | js.src = src; 8 | js.async = false; 9 | document.body.appendChild(js); 10 | }; 11 | 12 | loadScriptDefered(`http://maps.google.com/maps/api/js?key=${CFG.GMAPS.API_KEY}`); 13 | loadScriptDefered("/api/js/gmaps.js"); 14 | loadScriptDefered("/api/js/ajax.js"); 15 | loadScriptDefered("/api/js/main.js"); 16 | 17 | }))(); -------------------------------------------------------------------------------- /api/js/main.js: -------------------------------------------------------------------------------- 1 | ((() => { 2 | 3 | let loggedIn = false; 4 | let loginTimeout = null; 5 | 6 | let heartInterval = null; 7 | let heartTimeout = null; 8 | let heartTimedOut = true; 9 | 10 | const header = ` 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 37 | 41 | 44 | 63 | `; 64 | 65 | const gmap = new GMaps({ 66 | el: "#map", 67 | disableDoubleClickZoom: true, 68 | lat: 0, 69 | lng: 0, 70 | disableDefaultUI: true, 71 | dblclick(e) { 72 | vex.dialog.open({ 73 | message: "", 74 | input: header, 75 | buttons: [ 76 | $.extend({}, vex.dialog.buttons.YES, { 77 | text: "Submit" 78 | }), 79 | $.extend({}, vex.dialog.buttons.NO, { 80 | text: "Abort" 81 | }) 82 | ], 83 | callback(data) { 84 | if (data !== false && Object.keys(data).length) { 85 | const ed = e.data = {}; 86 | if (data.type === "SPAWN") { 87 | ed.interval = data.interval; 88 | ed.encounters = data.encounters; 89 | } 90 | else if (data.type === "CHECKPOINT") { 91 | ed.name = data.name; 92 | ed.description = data.description; 93 | ed.imageUrl = data.image_url; 94 | ed.experience = data.experience; 95 | } 96 | else if (data.type === "GYM") { 97 | ed.team = data.team; 98 | } 99 | e.type = data.type; 100 | addFort(e, ed); 101 | } 102 | } 103 | }) 104 | } 105 | }); 106 | 107 | function addFort(e, data) { 108 | let lat = e.latLng.lat(); 109 | let lng = e.latLng.lng(); 110 | let obj = { 111 | action: "addFortToPosition", 112 | latitude: lat, 113 | longitude: lng, 114 | zoom: gmap.zoom, 115 | type: e.type 116 | }; 117 | Object.assign(obj, data); 118 | send(obj, res => { 119 | console.log(res); 120 | refreshMapForts(); 121 | }); 122 | } 123 | 124 | function setStatus(txt, color) { 125 | connection_status.innerHTML = txt; 126 | connection_status.style.color = color; 127 | } 128 | 129 | setStatus("Connecting", "yellow"); 130 | 131 | send({ 132 | action: "init" 133 | }, res => { 134 | if (res.success) { 135 | setStatus("Connected!", "green"); 136 | } 137 | else { 138 | if (res.reason !== void 0) { 139 | setStatus(res.reason); 140 | } else { 141 | setStatus("Connection failed!", "red"); 142 | } 143 | return void 0; 144 | } 145 | }); 146 | 147 | login_attempt.addEventListener("click", login); 148 | 149 | submit_spawn.addEventListener("click", () => { 150 | send({ 151 | action: "spawnPkmnToPlayer", 152 | player: spawn_user.value, 153 | pkmn: spawn_pkmn.value 154 | }, res => { 155 | console.log(res); 156 | }); 157 | }); 158 | 159 | function login() { 160 | 161 | const username = login_username.value; 162 | const password = login_password.value; 163 | 164 | send({ 165 | action: "login", 166 | username, 167 | password 168 | }, res => { 169 | if (res.success) { 170 | afterLogin(); 171 | } 172 | else { 173 | setStatus("Login failed!", "red"); 174 | clearTimeout(loginTimeout); 175 | loginTimeout = setTimeout(() => { 176 | if (loggedIn) { 177 | setStatus("Connected!", "green"); 178 | } 179 | }, 3e3); 180 | } 181 | }); 182 | 183 | } 184 | 185 | function afterLogin() { 186 | loggedIn = true; 187 | login_area.style.display = "none"; 188 | setStatus("Logged in!", "green"); 189 | world_manager.style.display = "block"; 190 | server_ping.style.display = "block"; 191 | map_manager.style.display = "block"; 192 | gmap.refresh(); 193 | gmap.setCenter({ 194 | lat: CFG.GMAPS.BASE_LAT, 195 | lng: CFG.GMAPS.BASE_LNG 196 | }); 197 | gmap.setZoom(CFG.GMAPS.BASE_ZOOM); 198 | initHeartBeat(); 199 | refreshMapForts(); 200 | refreshConnectedPlayers(); 201 | getServerVersion(); 202 | } 203 | 204 | function refreshConnectedPlayers() { 205 | send({ 206 | action: "getConnectedPlayers" 207 | }, res => { 208 | connected_players.innerHTML = `Connected players: ${res.connected_players}`; 209 | }); 210 | } 211 | 212 | function getServerVersion() { 213 | send({ 214 | action: "getServerVersion" 215 | }, res => { 216 | server_version.innerHTML = `Server version: v${res.version}`; 217 | }); 218 | } 219 | 220 | function getFortIcon(fort) { 221 | if (fort.type === "CHECKPOINT") return ("/api/img/pokestop_blue.png"); 222 | else if (fort.uid[fort.uid.length - 1] === "S") return ("/api/img/spawn_point.png"); 223 | else return (`/api/img/gym_${fort.owned_by_team}.png`); 224 | } 225 | 226 | function refreshMapForts() { 227 | let center = gmap.getCenter(); 228 | let lat = center.lat(); 229 | let lng = center.lng(); 230 | send({ 231 | action: "getFortsByPosition", 232 | latitude: lat, 233 | longitude: lng, 234 | zoom: gmap.zoom 235 | }, result => { 236 | gmap.removeMarkers(); 237 | result.forts.map((fort) => { 238 | let icon = getFortIcon(fort); 239 | gmap.addMarker({ 240 | lat: fort.latitude, 241 | lng: fort.longitude, 242 | title: fort.name, 243 | icon, 244 | rightclick: function(e) { 245 | vex.dialog.confirm({ 246 | message: `

Delete this fort?
`, 247 | callback: function(value) { 248 | if (value) removeFort(this); 249 | }.bind(fort) 250 | }) 251 | }.bind(fort) 252 | }); 253 | }); 254 | }); 255 | } 256 | 257 | function removeFort(fort) { 258 | send({ 259 | action: "deleteFortById", 260 | uid: fort.uid, 261 | latitude: fort.latitude, 262 | longitude: fort.longitude, 263 | zoom: gmap.zoom 264 | }, res => { 265 | console.log(res); 266 | refreshMapForts(); 267 | }); 268 | } 269 | 270 | function initHeartBeat() { 271 | clearInterval(heartInterval); 272 | heartInterval = setInterval(() => { 273 | heartTimedOut = true; 274 | const now = +new Date(); 275 | heartTimeout = setTimeout(() => { 276 | if (heartTimedOut) { 277 | console.error("Heartbeat timeout!"); 278 | loggedIn = false; 279 | setStatus("Reconnecting..", "yellow"); 280 | login(); 281 | } 282 | }, 5e3); 283 | send({ 284 | action: "heartBeat", 285 | timestamp: now 286 | }, res => { 287 | if (res.timestamp) { 288 | heartTimedOut = false; 289 | clearTimeout(heartTimeout); 290 | const ping = res.timestamp - now; 291 | server_ping.innerHTML = `Ping: ${ping}ms`; 292 | refreshConnectedPlayers(); 293 | refreshMapForts(); 294 | } 295 | }); 296 | }, 3e3); 297 | } 298 | 299 | }))(); -------------------------------------------------------------------------------- /cfg.js.example: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export default { 4 | 5 | VERSION: JSON.parse(fs.readFileSync("./package.json")).version, 6 | 7 | // show greeting 8 | GREET: true, 9 | 10 | // Api things 11 | API_ENABLE: true, 12 | API_USERNAME: "root", 13 | API_PASSWORD: "", 14 | API_ALLOWED_HOSTS: ["localhost", "127.0.0.1"], 15 | 16 | // Server settings 17 | MAX_CONNECTIONS: 64, 18 | PORT: 3000, 19 | // If using vmware, vps or multiple network adapters, set the related ip here 20 | // otherwise leave it blank 21 | LOCAL_IP: "", 22 | GAME_MODE: 0, 23 | SAVE_INTERVAL: 1e4, 24 | // Better dont touch these 25 | TICK_INTERVAL: 1, 26 | // Timeouts 27 | BOOT_TIMEOUT: 1e4, 28 | PLAYER_CONNECTION_TIMEOUT: 1e3 * 60 * 30, 29 | CELL_TIMEOUT: 1e3 * 60, 30 | MINIMUM_CLIENT_VERSION: "0.35.0", 31 | DEFAULT_CONSOLE_COLOR: 32, 32 | TRANSFER_ACCOUNTS: false, 33 | 34 | // Server debug options 35 | DEBUG_DUMP_PATH: "logs/", 36 | DEBUG_DUMP_TRAFFIC: true, 37 | DEBUG_LOG_REQUESTS: true, 38 | 39 | // Choose a database type 40 | DATABASE_TYPE: "MYSQL", 41 | 42 | // MySQL credentials 43 | MYSQL_PORT: 3306, 44 | MYSQL_HOST_IP: "127.0.0.1", 45 | MYSQL_DB_NAME: "pogosql", 46 | MYSQL_USERNAME: "root", 47 | MYSQL_PASSWORD: "", 48 | MYSQL_GYM_TABLE: "gym", 49 | MYSQL_USERS_TABLE: "users", 50 | MYSQL_SPAWN_TABLE: "spawn_points", 51 | MYSQL_POKESTOP_TABLE: "pokestop", 52 | MYSQL_OWNED_PKMN_TABLE: "owned_pkmn", 53 | 54 | // Used for asset download session 55 | DOWNLOAD_PROVIDER: "GOOGLE", 56 | DOWNLOAD_USERNAME: "USERNAME", 57 | DOWNLOAD_PASSWORD: "PASSWORD", 58 | 59 | // Google maps api key 60 | GMAPS_KEY: "AIzaSyDF9rkP8lhcddBtvH9gVFzjnNo13WtmJIM", 61 | 62 | // Currently supported pokemon 63 | MAX_POKEMON_NATIONAL_ID: 151, 64 | DUMP_ASSET_PATH: "data/" 65 | 66 | } -------------------------------------------------------------------------------- /install-windows.bat: -------------------------------------------------------------------------------- 1 | set LIBPROTOBUF=%CD%\protobuf 2 | npm install node-protobuf && npm install -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "POGOServer", 3 | "version": "0.6.1", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/maierfelix/POGOServer.git" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\"", 11 | "babel-node": "babel-node --presets=es2015", 12 | "boot": "npm run babel-node -- ./src/index.js", 13 | "api": "npm run babel-node -- ./api/index.js", 14 | "update": "npm run babel-node -- ./updater.js" 15 | }, 16 | "engines": { 17 | "node": ">= 6.x", 18 | "npm": ">= 3.x" 19 | }, 20 | "author": "Felix Maier", 21 | "license": "GNU GPL v3", 22 | "dependencies": { 23 | "babel-cli": "^6.11.4", 24 | "babel-preset-es2015": "^6.13.1", 25 | "babel-preset-stage-0": "^6.5.0", 26 | "directory-tree": "^1.1.1", 27 | "fs-extra": "^0.30.0", 28 | "jwt-decode": "^2.1.0", 29 | "mysql": "^2.11.1", 30 | "nodegit": "^0.16.0", 31 | "node-pogo-protos": "^1.4.0", 32 | "pcrypt": "git+https://github.com/laverdet/pcrypt.git", 33 | "pogo-asset-downloader": "^0.3.1", 34 | "pokerare": "^0.1.1", 35 | "pokemongo-protobuf": "^1.11.0", 36 | "prompt": "^1.0.0", 37 | "s2-geometry": "^1.2.9", 38 | "url": "^0.11.0" 39 | }, 40 | "devDependencies": {} 41 | } 42 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import url from "url"; 3 | import s2 from "s2-geometry"; 4 | 5 | import Cell from "./models/World/Cell"; 6 | 7 | import print from "./print"; 8 | import CFG from "../cfg"; 9 | 10 | import { 11 | getHashCodeFrom 12 | } from "./utils"; 13 | 14 | const S2Geo = s2.S2; 15 | 16 | let showHint = true; 17 | 18 | export function processApiCall(req, res, route) { 19 | 20 | let allowedHosts = CFG.API_ALLOWED_HOSTS; 21 | 22 | let hoster = url.parse(req.headers.referer).host; 23 | 24 | if (!(allowedHosts.indexOf(hoster) > -1)) { 25 | print(`Denied API access for ${hoster}!`, 31); 26 | if (showHint) { 27 | print(`To grant ${hoster} API access, add it to the allowed hosts in cfg.js`, 33); 28 | showHint = false; 29 | } 30 | let result = { 31 | success:false, 32 | reason: "API access denied!" 33 | }; 34 | this.answerApiCall(res, JSON.stringify(result)); 35 | return void 0; 36 | } 37 | 38 | let raw = req.body.toString(); 39 | let json = null; 40 | 41 | try { 42 | json = JSON.parse(raw); 43 | } catch (e) { 44 | print(e, 31); 45 | this.answerApiCall(res, ""); 46 | return void 0; 47 | } 48 | 49 | if (this.isApiCall(json)) { 50 | json.host = hoster; 51 | if (json.action === "login") { 52 | this["api_login"](json).then((result) => { 53 | this.answerApiCall(res, JSON.stringify(result)); 54 | }); 55 | } 56 | else { 57 | if (this.apiClients[hoster]) { 58 | this["api_" + json.action](json).then((result) => { 59 | this.answerApiCall(res, JSON.stringify(result)); 60 | }); 61 | } 62 | else { 63 | print(`${hoster} isnt logged in! Kicking..`, 31); 64 | } 65 | } 66 | } 67 | else { 68 | if (json.action === "init") { 69 | this.answerApiCall(res, JSON.stringify({ success: true })); 70 | } 71 | } 72 | 73 | } 74 | 75 | export function answerApiCall(res, data) { 76 | res.setHeader("Access-Control-Allow-Origin", "*"); 77 | res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE"); 78 | res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type"); 79 | res.setHeader("Access-Control-Allow-Credentials", true); 80 | res.end(data); 81 | } 82 | 83 | export function api_login(data) { 84 | 85 | if (typeof data !== "object") return void 0; 86 | 87 | let success = false; 88 | 89 | let username = CFG.API_USERNAME; 90 | let password = CFG.API_PASSWORD; 91 | 92 | if ( 93 | username === data.username && 94 | password === data.password 95 | ) { 96 | success = true; 97 | if (!this.apiClients[data.host]) { 98 | print(`API access for ${data.host} granted!`); 99 | } 100 | print(`${data.host} logged in!`, 36); 101 | this.apiClients[data.host] = { 102 | timestamp: +new Date() 103 | }; 104 | } 105 | 106 | return new Promise((resolve) => { 107 | resolve({ 108 | success: success 109 | }); 110 | }); 111 | 112 | } 113 | 114 | export function api_heartBeat() { 115 | return new Promise((resolve) => { 116 | resolve({ 117 | timestamp: +new Date() 118 | }); 119 | }); 120 | } 121 | 122 | export function api_getConnectedPlayers() { 123 | return new Promise((resolve) => { 124 | resolve({ 125 | connected_players: this.world.connectedPlayers 126 | }); 127 | }); 128 | } 129 | 130 | export function api_getServerVersion() { 131 | return new Promise((resolve) => { 132 | resolve({ 133 | version: CFG.VERSION 134 | }); 135 | }); 136 | } 137 | 138 | export function api_spawnPkmnToPlayer(data) { 139 | let name = String(data.player); 140 | let pkmn = String(data.pkmn).toUpperCase(); 141 | print(`Spawned 1x ${pkmn}'s to ${name}!`); 142 | return new Promise((resolve) => { 143 | resolve({ 144 | success: true 145 | }); 146 | }); 147 | } 148 | 149 | export function api_addFortToPosition(data) { 150 | return new Promise((resolve) => { 151 | this.world.insertFortIntoDatabase(data).then((fort) => { 152 | resolve({ 153 | success: true 154 | }); 155 | }); 156 | }); 157 | } 158 | 159 | export function api_deleteFortById(data) { 160 | let uid = data.uid; 161 | let cellId = Cell.getIdByPosition(data.latitude, data.longitude, data.zoom); 162 | return new Promise((resolve) => { 163 | this.world.deleteFort(cellId, uid).then(() => { 164 | resolve({ 165 | id: cellId + "." + uid, 166 | success: true 167 | }); 168 | }); 169 | }); 170 | } 171 | 172 | export function api_getFortsByPosition(data) { 173 | return new Promise((resolve) => { 174 | let lat = data.latitude; 175 | let lng = data.longitude; 176 | let zoom = data.zoom; 177 | this.getNeighboredForts(this.getNeighbors(lat, lng, zoom), [], 0).then((forts) => { 178 | let result = []; 179 | forts.map((fort) => { 180 | let fortData = fort.serialize(); 181 | fortData.name = fort.name; 182 | fortData.uid = fort.uid; 183 | result.push(fortData); 184 | }); 185 | resolve({ 186 | forts: result, 187 | success: true 188 | }); 189 | }); 190 | }); 191 | } 192 | 193 | export function getNeighbors(lat, lng, lvl) { 194 | let origin = S2Geo.latLngToKey(lat, lng, lvl); 195 | let walk = [S2Geo.keyToId(origin)]; 196 | let next = S2Geo.nextKey(origin); 197 | let prev = S2Geo.prevKey(origin); 198 | let ii = 0; 199 | let length = 10; 200 | for (; ii < length; ++ii) { 201 | walk.push(S2Geo.toId(prev)); 202 | walk.push(S2Geo.toId(next)); 203 | next = S2Geo.nextKey(next); 204 | prev = S2Geo.prevKey(prev); 205 | }; 206 | walk.sort((a, b) => a - b); 207 | return (walk); 208 | } 209 | 210 | export function getNeighboredForts(cells, out, index) { 211 | return new Promise((resolve) => { 212 | let id = cells[index]; 213 | this.world.getFortsByCellId(id).then((cell) => { 214 | out = out.concat(cell.forts); 215 | if (++index >= cells.length) resolve(out); 216 | else resolve(this.getNeighboredForts(cells, out, index)); 217 | }); 218 | }); 219 | } -------------------------------------------------------------------------------- /src/commands.js: -------------------------------------------------------------------------------- 1 | import print from "./print"; 2 | import CFG from "../cfg"; 3 | 4 | export function saveAllPlayers() { 5 | if (this.world.players.length > 0) { 6 | for (let player of this.world.players) { 7 | this.savePlayer(player); 8 | }; 9 | } 10 | } 11 | 12 | /** 13 | * @param {Player} player 14 | */ 15 | export function savePlayer(player) { 16 | return new Promise((resolve) => { 17 | if (player.authenticated) { 18 | this.updateUser(player).then(resolve); 19 | } 20 | }); 21 | } 22 | 23 | /** 24 | * @param {Player} player 25 | */ 26 | export function updateUser(player) { 27 | let query = this.getUserQuery("UPDATE", "WHERE email=? LIMIT 1"); 28 | let data = this.getUserQueryData(player); 29 | 30 | return new Promise((resolve) => { 31 | this.world.instance.db.query(query, data, (e, res) => { 32 | resolve(); 33 | }); 34 | }); 35 | } 36 | 37 | /** 38 | * @param {Object} obj 39 | * @return {Array} 40 | */ 41 | export function getUserQueryData(obj) { 42 | return ([ 43 | obj.username, 44 | obj.email, 45 | obj.info._exp, 46 | obj.info._level, 47 | obj.info.stardust, 48 | obj.info.pokecoins, 49 | obj.info._team, 50 | // position 51 | obj.latitude, 52 | obj.longitude, 53 | obj.altitude, 54 | // contact settings 55 | 0, //obj.contact.sendMarketingEmails, 56 | 0, //obj.contact.sendPushNotifications, 57 | // inventory 58 | obj.candyBag.querify(), //obj.candyBag, 59 | obj.bag.querify(), //obj.bag, 60 | obj.avatar.querify(), //obj.avatar, 61 | '{"0":1,"1":1,"3":1,"4":1,"7":1}', //obj.tutorial, 62 | // WHERE 63 | obj.email 64 | ]); 65 | } 66 | -------------------------------------------------------------------------------- /src/cycle.js: -------------------------------------------------------------------------------- 1 | import print from "./print"; 2 | import CFG from "../cfg"; 3 | 4 | import Settings from "./modes"; 5 | const MAP_REFRESH_RATE = Settings.GAME_SETTINGS.map_settings.get_map_objects_max_refresh_seconds; 6 | 7 | export function startCycle() { 8 | this.cycleInstance = setTimeout(() => this.cycle(), CFG.TICK_INTERVAL); 9 | } 10 | 11 | export function stopCycle() { 12 | clearTimeout(this.cycleInstance); 13 | } 14 | 15 | export function cycle() { 16 | 17 | this.stopCycle(); 18 | this.startCycle(); 19 | 20 | if (this.STATES.PAUSE === true) return void 0; 21 | if (this.STATES.CRASH === true) return void 0; 22 | 23 | this.updateTimers(); 24 | 25 | if (this.passedTicks <= 0) return void 0; 26 | 27 | this.resetTimers(); 28 | 29 | return void 0; 30 | 31 | } 32 | 33 | export function updateTimers() { 34 | let local = Date.now(); 35 | this.passedTicks = local - this.time; 36 | this.tick += this.passedTicks; 37 | this.time = local; 38 | return void 0; 39 | } 40 | 41 | export function resetTimers() { 42 | if (this.tick >= 1e4) { 43 | this.fullTick++; 44 | if (this.fullTick >= 2) { 45 | this.fullTick = 0; 46 | } 47 | this.tick = 0; 48 | // Timeout ticks, not precise 49 | this.playerTimeoutTick(); 50 | this.cellTimeoutTick(); 51 | } 52 | this.saveTick++; 53 | // Save interval 54 | if (this.saveTick >= CFG.SAVE_INTERVAL) { 55 | this.saveAllPlayers(); 56 | this.saveTick = 0; 57 | } 58 | this.spawnTick++; 59 | // Pkmn spawn interval 60 | if (this.spawnTick >= MAP_REFRESH_RATE * 1e3) { 61 | this.world.refreshSpawns(); 62 | this.spawnTick = 0; 63 | } 64 | return void 0; 65 | } 66 | 67 | export function playerTimeoutTick() { 68 | let maxTimeout = CFG.PLAYER_CONNECTION_TIMEOUT; 69 | let ii = 0; 70 | let length = this.world.connectedPlayers; 71 | let player = null; 72 | let players = this.world.players; 73 | for (; ii < length; ++ii) { 74 | player = players[ii]; 75 | if (this.time - player.timeout >= maxTimeout) { 76 | print(`${player.remoteAddress} timed out`, 34); 77 | this.savePlayer(player); 78 | this.removePlayer(player); 79 | } 80 | }; 81 | } 82 | 83 | export function cellTimeoutTick() { 84 | let ii = 0; 85 | let length = this.world.cells.length; 86 | let cell = null; 87 | for (; ii < length; ++ii) { 88 | cell = this.world.cells[ii]; 89 | if (cell.expiration - +new Date() <= 0) { 90 | cell.delete(); 91 | length--; 92 | } 93 | }; 94 | } -------------------------------------------------------------------------------- /src/db/create.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | import print from "../print"; 4 | import CFG from "../../cfg"; 5 | 6 | export function createTableIfNotExists(name) { 7 | return new Promise((resolve) => { 8 | this.db.query(`SHOW TABLES LIKE '${name}';`, (e, rows, fields) => { 9 | if (e) print(e, 31); 10 | else { 11 | // exists 12 | if (rows && rows.length) resolve(); 13 | // create user table 14 | else this.createTables().then(resolve); 15 | } 16 | }); 17 | }); 18 | } 19 | 20 | /** 21 | * @param {String} name 22 | */ 23 | export function createTables() { 24 | return new Promise((resolve) => { 25 | this.createTable(CFG.MYSQL_GYM_TABLE).then(() => { 26 | this.createTable(CFG.MYSQL_USERS_TABLE).then(() => { 27 | this.createTable(CFG.MYSQL_SPAWN_TABLE).then(() => { 28 | this.createTable(CFG.MYSQL_POKESTOP_TABLE).then(() => { 29 | this.createTable(CFG.MYSQL_OWNED_PKMN_TABLE).then(() => { 30 | resolve(); 31 | }); 32 | }); 33 | }); 34 | }); 35 | }); 36 | }); 37 | } 38 | 39 | export function createTable(name) { 40 | 41 | print(`Creating table ${name}`, 36); 42 | 43 | let query = ` 44 | CREATE TABLE IF NOT EXISTS ${name} ( 45 | ${fs.readFileSync(__dirname + "/tables/" + name + ".table", "utf8")} 46 | ) ENGINE=InnoDB; 47 | `; 48 | 49 | return new Promise((resolve) => { 50 | this.db.query(query, (e, rows) => { 51 | if (e) print(e, 31); 52 | else resolve(); 53 | }); 54 | }); 55 | 56 | } -------------------------------------------------------------------------------- /src/db/get.js: -------------------------------------------------------------------------------- 1 | import print from "../print"; 2 | import CFG from "../../cfg"; 3 | 4 | /** 5 | * @param {String} column 6 | * @param {String} value 7 | * @param {String} table 8 | */ 9 | export function getQueryByColumnFromTable(column, value, table) { 10 | return new Promise((resolve) => { 11 | this.db.query(`SELECT * FROM ${table} WHERE ${column}=?`, [value], (e, rows) => { 12 | if (e) print(e, 31); 13 | if (rows && rows.length) resolve(rows); 14 | else resolve(void 0); 15 | }); 16 | }); 17 | } 18 | 19 | /** 20 | * @param {String} column 21 | * @param {String} value 22 | * @param {String} table 23 | */ 24 | export function deleteQueryByColumnFromTable(column, value, table) { 25 | return new Promise((resolve) => { 26 | this.db.query(`DELETE FROM ${table} WHERE ${column}=?`, [value], (e, rows) => { 27 | if (e) print(e, 31); 28 | else resolve(void 0); 29 | }); 30 | }); 31 | } -------------------------------------------------------------------------------- /src/db/index.js: -------------------------------------------------------------------------------- 1 | import mysql from "mysql"; 2 | 3 | import print from "../print"; 4 | import CFG from "../../cfg"; 5 | 6 | export function setupDatabaseConnection() { 7 | 8 | let connection = mysql.createConnection({ 9 | host : CFG.MYSQL_HOST_IP, 10 | port : CFG.MYSQL_PORT, 11 | database : CFG.MYSQL_DB_NAME, 12 | user : CFG.MYSQL_USERNAME, 13 | password : CFG.MYSQL_PASSWORD 14 | }); 15 | 16 | return new Promise((resolve) => { 17 | connection.connect((error) => { 18 | if (error) { 19 | print("MySQL " + error, 31); 20 | this.retry("Retrying again in ", () => this.setupDatabaseConnection().then(resolve), 5); 21 | return void 0; 22 | } 23 | this.db = connection; 24 | this.createTableIfNotExists(CFG.MYSQL_USERS_TABLE).then(() => { 25 | this.createTableIfNotExists(CFG.MYSQL_OWNED_PKMN_TABLE).then(() => { 26 | print(`\x1b[36;1mMySQL\x1b[0m\x1b[32;1m connection established\x1b[0m`); 27 | resolve(); 28 | }); 29 | }); 30 | }); 31 | connection.on("error", (error) => { 32 | print("MySQL " + error, 31); 33 | this.retry("Trying to reconnect in ", () => this.setupDatabaseConnection().then(resolve), 5); 34 | }); 35 | }); 36 | 37 | } 38 | 39 | /** 40 | * @param {Function} resolve 41 | */ 42 | export function closeConnection(resolve) { 43 | this.db.end(() => { 44 | resolve(); 45 | }); 46 | } -------------------------------------------------------------------------------- /src/db/query.js: -------------------------------------------------------------------------------- 1 | import CFG from "../../cfg"; 2 | 3 | /** 4 | * @return {String} 5 | */ 6 | export function getUserQuery(cmd, after) { 7 | return (` 8 | ${cmd} ${CFG.MYSQL_USERS_TABLE} 9 | SET 10 | username=?, 11 | email=?, 12 | exp=?, 13 | level=?, 14 | stardust=?, 15 | pokecoins=?, 16 | team=?, 17 | latitude=?, 18 | longitude=?, 19 | altitude=?, 20 | send_marketing_emails=?, 21 | send_push_notifications=?, 22 | candies=?, 23 | items=?, 24 | avatar=?, 25 | tutorial=? 26 | ${after} 27 | `); 28 | } 29 | 30 | /** 31 | * @param {Player} player 32 | * @return {Array} 33 | */ 34 | export function getPlayerQueryData(player) { 35 | return ([ 36 | player.username, 37 | player.email, 38 | player.exp, 39 | player.level, 40 | player.stardust, 41 | player.pokecoins, 42 | player.team, 43 | // position 44 | player.latitude, 45 | player.longitude, 46 | player.altitude, 47 | // contact settings 48 | player.send_marketing_emails, 49 | player.send_push_notifications, 50 | // json 51 | player.candies.serialize(), 52 | player.items.serialize(), 53 | player.avatar.serialize(), 54 | player.tutorial.serialize() 55 | ]); 56 | } 57 | 58 | /** 59 | * @return {String} 60 | */ 61 | export function getOwnedPkmnQuery(cmd, after) { 62 | return (` 63 | ${cmd} ${CFG.MYSQL_OWNED_PKMN_TABLE} 64 | SET 65 | owner_id=?, 66 | pokemon_id=?, 67 | cp=?, 68 | stamina=?, 69 | stamina_max=?, 70 | move_1=?, 71 | move_2=?, 72 | deployed_fort_id=?, 73 | is_egg=?, 74 | egg_km_walked_target=?, 75 | egg_km_walked_start=?, 76 | origin=?, 77 | height_m=?, 78 | weight_kg=?, 79 | individual_attack=?, 80 | individual_defense=?, 81 | individual_stamina=?, 82 | cp_multiplier=?, 83 | pokeball=?, 84 | captured_cell_id=?, 85 | battles_attacked=?, 86 | battles_defended=?, 87 | egg_incubator_id=?, 88 | creation_time_ms=?, 89 | num_upgrades=?, 90 | additional_cp_multiplier=?, 91 | favorite=?, 92 | nickname=?, 93 | from_fort=? 94 | ${after} 95 | `); 96 | } 97 | 98 | /** 99 | * @param {Object} obj 100 | * @return {Array} 101 | */ 102 | export function getOwnedPkmnQueryData(obj) { 103 | return ([ 104 | obj.owner_id, 105 | obj.pokemon_id, 106 | obj.cp, 107 | obj.stamina, 108 | obj.stamina_max, 109 | obj.move_1, 110 | obj.move_2, 111 | obj.deployed_fort_id || "", 112 | obj.is_egg || 0, 113 | obj.egg_km_walked_target || 0, 114 | obj.egg_km_walked_start || 0, 115 | obj.origin || 0, 116 | obj.height_m, 117 | obj.weight_kg, 118 | obj.individual_attack, 119 | obj.individual_defense, 120 | obj.individual_stamina, 121 | obj.cp_multiplier, 122 | obj.pokeball, 123 | obj.captured_cell_id || 0, 124 | obj.battles_attacked || 0, 125 | obj.battles_defended || 0, 126 | obj.egg_incubator_id || "", 127 | obj.creation_time_ms, 128 | obj.num_upgrades || 0, 129 | obj.additional_cp_multiplier || 0, 130 | obj.favorite || 0, 131 | obj.nickname || "", 132 | obj.from_fort || 0 133 | ]); 134 | } -------------------------------------------------------------------------------- /src/db/tables/gym.table: -------------------------------------------------------------------------------- 1 | id int(11) NOT NULL AUTO_INCREMENT, 2 | cell_id varchar(64) NOT NULL, 3 | latitude double NOT NULL, 4 | longitude double NOT NULL, 5 | team int(1) NOT NULL, 6 | in_battle tinyint(1) NOT NULL DEFAULT '0', 7 | points int(11) NOT NULL DEFAULT '0', 8 | PRIMARY KEY (id) -------------------------------------------------------------------------------- /src/db/tables/owned_pkmn.table: -------------------------------------------------------------------------------- 1 | id int(11) NOT NULL AUTO_INCREMENT, 2 | owner_id int(11) NOT NULL, 3 | dex_number int(11) NOT NULL, 4 | cp int(32) NOT NULL, 5 | stamina int(11) NOT NULL, 6 | stamina_max int(11) NOT NULL, 7 | move_1 varchar(32) NOT NULL, 8 | move_2 varchar(32) NOT NULL, 9 | deployed_fort_id longtext NULL DEFAULT NULL, 10 | is_egg tinyint(1) NULL DEFAULT NULL, 11 | egg_km_walked_target double NULL DEFAULT NULL, 12 | egg_km_walked_start double NULL DEFAULT NULL, 13 | origin int(11) NULL DEFAULT NULL, 14 | height_m double NOT NULL, 15 | weight_kg double NOT NULL, 16 | individual_attack int(11) NOT NULL, 17 | individual_defense int(11) NOT NULL, 18 | individual_stamina int(11) NOT NULL, 19 | cp_multiplier double NOT NULL, 20 | pokeball varchar(32) NOT NULL, 21 | captured_cell_id varchar(32) NULL DEFAULT NULL, 22 | battles_attacked int(11) NULL DEFAULT NULL, 23 | battles_defended int(11) NULL DEFAULT NULL, 24 | egg_incubator_id longtext NULL DEFAULT NULL, 25 | creation_time_ms bigint(20) NULL DEFAULT NULL, 26 | num_upgrades int(11) NULL DEFAULT NULL, 27 | additional_cp_multiplier double NULL DEFAULT NULL, 28 | favorite tinyint(1) NULL DEFAULT NULL, 29 | nickname longtext NULL DEFAULT NULL, 30 | from_fort int(11) NULL DEFAULT NULL, 31 | PRIMARY KEY (id) -------------------------------------------------------------------------------- /src/db/tables/pokestop.table: -------------------------------------------------------------------------------- 1 | id int(11) NOT NULL AUTO_INCREMENT, 2 | cell_id varchar(64) NOT NULL, 3 | latitude double NOT NULL, 4 | longitude double NOT NULL, 5 | name varchar(64) NOT NULL, 6 | description varchar(128) NOT NULL, 7 | image_url varchar(128) NOT NULL, 8 | experience int(11) NOT NULL DEFAULT '0', 9 | rewards varchar(64) NOT NULL DEFAULT '{}', 10 | PRIMARY KEY (id) -------------------------------------------------------------------------------- /src/db/tables/spawn_points.table: -------------------------------------------------------------------------------- 1 | id int(11) NOT NULL AUTO_INCREMENT, 2 | cell_id varchar(64) NOT NULL, 3 | latitude double NOT NULL DEFAULT '0', 4 | longitude double NOT NULL DEFAULT '0', 5 | encounters varchar(64) NOT NULL DEFAULT '{}', 6 | update_interval int(11) NOT NULL DEFAULT '5', 7 | min_spawn_expire int(11) NOT NULL DEFAULT '2', 8 | max_spawn_expire int(11) NOT NULL DEFAULT '15', 9 | PRIMARY KEY (id) -------------------------------------------------------------------------------- /src/db/tables/users.table: -------------------------------------------------------------------------------- 1 | id int(11) NOT NULL AUTO_INCREMENT, 2 | username varchar(16) NOT NULL, 3 | email varchar(32) NOT NULL, 4 | exp int(11) NULL DEFAULT NULL, 5 | level smallint(11) NULL DEFAULT NULL, 6 | stardust int(11) NULL DEFAULT NULL, 7 | pokecoins int(11) NULL DEFAULT NULL, 8 | team tinyint(11) NULL DEFAULT NULL, 9 | latitude double NULL DEFAULT NULL, 10 | longitude double NULL DEFAULT NULL, 11 | altitude double NULL DEFAULT NULL, 12 | send_marketing_emails tinyint(1) NULL DEFAULT NULL, 13 | send_push_notifications tinyint(1) NULL DEFAULT NULL, 14 | candies varchar(1024) NOT NULL DEFAULT '{}', 15 | items varchar(255) NOT NULL DEFAULT '{}', 16 | avatar varchar(128) NOT NULL DEFAULT '{}', 17 | pokedex varchar(64) NOT NULL DEFAULT '{}', 18 | tutorial varchar(64) NOT NULL DEFAULT '{"0":1,"1":1,"3":1,"4":1,"7":1}', 19 | PRIMARY KEY (id) 20 | -------------------------------------------------------------------------------- /src/dump.js: -------------------------------------------------------------------------------- 1 | import fse from "fs-extra"; 2 | import POGOProtos from "pokemongo-protobuf"; 3 | 4 | import print from "./print"; 5 | import CFG from "../cfg"; 6 | 7 | import { _toCC } from "./utils"; 8 | 9 | /** 10 | * @param {Request} req 11 | * @param {Array} res 12 | * @return {Object} 13 | */ 14 | export function decode(req, res) { 15 | 16 | // clone 17 | req = JSON.parse(JSON.stringify(req)); 18 | res = JSON.parse(JSON.stringify(res)); 19 | 20 | // dont decode unknown6, since it bloats the file size 21 | delete req.unknown6; 22 | 23 | // decode requests 24 | for (let request of req.requests) { 25 | let key = _toCC(request.request_type); 26 | let msg = request.request_message; 27 | if (msg) { 28 | let proto = `POGOProtos.Networking.Requests.Messages.${key}Message`; 29 | request.request_message = this.parseProtobuf(new Buffer(msg.data), proto); 30 | } 31 | }; 32 | 33 | // decode responses 34 | let index = 0; 35 | for (let resp of res) { 36 | let key = _toCC(req.requests[index].request_type); 37 | let msg = new Buffer(resp); 38 | let proto = `POGOProtos.Networking.Responses.${key}Response`; 39 | res[index] = this.parseProtobuf(msg, proto); 40 | index++; 41 | }; 42 | 43 | // clone again to build response out of it 44 | let req2 = JSON.parse(JSON.stringify(req)); 45 | 46 | // build res base out of req 47 | delete req2.requests; 48 | req2.returns = res; 49 | req2.status_code = 1; 50 | 51 | return ({ 52 | req: req, 53 | res: res 54 | }); 55 | 56 | } 57 | 58 | /** 59 | * @param {Request} req 60 | * @param {Response} res 61 | */ 62 | export function dumpTraffic(req, res) { 63 | try { 64 | let decoded = this.decode(req, res); 65 | let out = { 66 | Request: decoded.req, 67 | Response: decoded.res 68 | }; 69 | decoded = JSON.stringify(out, null, 2); 70 | fse.outputFileSync(CFG.DEBUG_DUMP_PATH + Date.now(), decoded); 71 | } catch (e) { 72 | print("Dump traffic: " + e, 31); 73 | } 74 | } -------------------------------------------------------------------------------- /src/enum.js: -------------------------------------------------------------------------------- 1 | import proto from "node-pogo-protos"; 2 | 3 | export default { 4 | TEAM: proto.Enums.TeamColor, 5 | ITEMS: proto.Inventory.Item.ItemId, 6 | GENDER: proto.Enums.Gender, 7 | TUTORIAL: proto.Enums.TutorialState, 8 | POKEMON_IDS: proto.Enums.PokemonId, 9 | POKEMON_FAMILY: proto.Enums.PokemonFamilyId, 10 | getNameById: (emu, id) => { 11 | id <<= 0; 12 | for (let key in emu) { 13 | if (emu[key] === id) return (key); 14 | }; 15 | return (null); 16 | }, 17 | getIdByName: (emu, name) => { 18 | for (let key in emu) { 19 | if (key === name) return (emu[key]); 20 | }; 21 | return (null); 22 | } 23 | } -------------------------------------------------------------------------------- /src/http.js: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | 3 | import print from "./print"; 4 | import CFG from "../cfg"; 5 | 6 | /** 7 | * @return {HTTP} 8 | */ 9 | export function createHTTPServer() { 10 | let server = http.createServer((req, res) => { 11 | if (this.world.isFull()) { 12 | print(`Server is full! Refused ${req.headers.host}`, 31); 13 | return void 0; 14 | } 15 | let chunks = []; 16 | req.on("data", (chunk) => { 17 | chunks.push(chunk); 18 | }); 19 | req.on("end", () => { 20 | let buffer = Buffer.concat(chunks); 21 | req.body = buffer; 22 | this.routeRequest(req, res); 23 | }); 24 | }); 25 | server.listen(CFG.PORT); 26 | return (server); 27 | } 28 | 29 | export function shutdown() { 30 | this.socket.close(() => { 31 | print("Closed http server!", 33); 32 | this.closeConnection(() => { 33 | print("Closed database connection!", 33); 34 | print("Server shutdown!", 31); 35 | setTimeout(() => process.exit(1), 2e3); 36 | }); 37 | }); 38 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import os from "os"; 3 | import request from "request"; 4 | import readline from "readline"; 5 | import POGOProtos from "pokemongo-protobuf"; 6 | 7 | import { 8 | _toCC, 9 | deXOR, 10 | inherit, 11 | getHashCodeFrom 12 | } from "./utils"; 13 | 14 | import print from "./print"; 15 | import CFG from "../cfg"; 16 | 17 | import World from "./models/World"; 18 | 19 | import * as _api from "./api"; 20 | import * as _commands from "./commands"; 21 | import * as _dump from "./dump"; 22 | import * as _http from "./http"; 23 | import * as _setup from "./setup"; 24 | import * as _cycle from "./cycle"; 25 | import * as _request from "./request"; 26 | import * as _response from "./response"; 27 | import * as _process from "./process"; 28 | import * as _mysql from "./db/index"; 29 | import * as _mysql_get from "./db/get"; 30 | import * as _mysql_query from "./db/query"; 31 | import * as _mysql_create from "./db/create"; 32 | 33 | const greetMessage = fs.readFileSync(".greet", "utf8"); 34 | 35 | /** 36 | * @class GameServer 37 | */ 38 | export default class GameServer { 39 | 40 | /** @constructor */ 41 | constructor() { 42 | 43 | this.STATES = { 44 | PAUSE: false, 45 | DEBUG: false, 46 | CRASH: false 47 | }; 48 | 49 | this.db = null; 50 | 51 | this.repository = null; 52 | 53 | this.apiClients = {}; 54 | 55 | this.socket = null; 56 | this.cycleInstance = null; 57 | 58 | // Timer things 59 | this.tick = 0; 60 | this.time = 0; 61 | this.fullTick = 0; 62 | this.saveTick = 0; 63 | this.spawnTick = 0; 64 | this.timeoutTick = 0; 65 | this.passedTicks = 0; 66 | 67 | if (CFG.GREET) this.greet(); 68 | 69 | this.getLatestVersion().then((latest) => { 70 | print(this.repository); 71 | let current = require("../package.json").version; 72 | print(`Booting Server v${current}`, 33); 73 | if (current < latest) { 74 | print(`WARNING: Please update to the latest build v${latest}!`, 33); 75 | } 76 | this.setup().then(() => { 77 | this.world = new World(this); 78 | }); 79 | }); 80 | 81 | } 82 | 83 | fetchVersioningUrl() { 84 | return new Promise((resolve) => { 85 | let url = ""; 86 | let branch = "master"; 87 | let base = "https://raw.githubusercontent.com"; 88 | url = require("../package.json").repository.url; 89 | url = url.replace("git://", ""); 90 | url = url.replace(".git", ""); 91 | url = url.replace("github.com/", ""); 92 | this.repository = `https://github.com/${url}`; 93 | url = `${base}/${url}/${branch}/package.json`; 94 | resolve(url); 95 | }); 96 | } 97 | 98 | getLatestVersion() { 99 | return new Promise((resolve) => { 100 | this.fetchVersioningUrl().then((url) => { 101 | request({url: url}, (error, response, body) => { 102 | let json = null; 103 | try { 104 | json = JSON.parse(body); 105 | } catch (e) { 106 | json = {}; 107 | print(e, 31); 108 | } 109 | resolve(json.version); 110 | }); 111 | }); 112 | }); 113 | } 114 | 115 | /** 116 | * @param {Object} obj 117 | * @return {Boolean} 118 | */ 119 | isApiCall(call) { 120 | let action = String(call.action); 121 | return ( 122 | "api_" + action in _api 123 | ); 124 | } 125 | 126 | /** 127 | * @param {String} msg 128 | * @param {Function} fn 129 | * @param {Number} timer 130 | */ 131 | retry(msg, fn, timer) { 132 | process.stdout.clearLine(); 133 | process.stdout.cursorTo(0); 134 | print(`${msg}${timer}s`, 33, true); 135 | if (timer >= 1) setTimeout(() => this.retry(msg, fn, --timer), 1e3); 136 | else { 137 | process.stdout.write("\n"); 138 | fn(); 139 | } 140 | } 141 | 142 | /** 143 | * @return {String} 144 | */ 145 | getLocalIPv4() { 146 | let address = null; 147 | let interfaces = os.networkInterfaces(); 148 | for (var dev in interfaces) { 149 | interfaces[dev].filter((details) => details.family === "IPv4" && details.internal === false ? address = details.address: void 0); 150 | }; 151 | return (address); 152 | } 153 | 154 | /** 155 | * @param {Buffer} buffer 156 | * @param {String} schema 157 | */ 158 | parseProtobuf(buffer, schema) { 159 | try { 160 | return POGOProtos.parseWithUnknown(buffer, schema); 161 | } catch (e) { 162 | print(e, 31); 163 | } 164 | } 165 | 166 | /** 167 | * @param {String} path 168 | * @return {Boolean} 169 | */ 170 | fileExists(path) { 171 | try { 172 | fs.statSync(path); 173 | } catch (e) { 174 | return (false); 175 | } 176 | return (true); 177 | } 178 | 179 | /** 180 | * @return {String} 181 | */ 182 | getCurrentTime() { 183 | let date = new Date(); 184 | return ( 185 | `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` 186 | ); 187 | } 188 | 189 | greet() { 190 | console.log(greetMessage); 191 | } 192 | 193 | } 194 | 195 | inherit(GameServer, _api); 196 | inherit(GameServer, _commands); 197 | inherit(GameServer, _dump); 198 | inherit(GameServer, _http); 199 | inherit(GameServer, _setup); 200 | inherit(GameServer, _cycle); 201 | inherit(GameServer, _request); 202 | inherit(GameServer, _response); 203 | inherit(GameServer, _process); 204 | inherit(GameServer, _mysql); 205 | inherit(GameServer, _mysql_get); 206 | inherit(GameServer, _mysql_query); 207 | inherit(GameServer, _mysql_create); 208 | 209 | (() => { 210 | 211 | const server = new GameServer(); 212 | 213 | const rl = readline.createInterface({ 214 | input: process.stdin, 215 | output: process.stdout, 216 | terminal: false 217 | }); 218 | 219 | rl.on("line", (data) => { 220 | server.stdinInput(data); 221 | }); 222 | 223 | process.on("uncaughtException", (data) => { 224 | server.uncaughtException(data); 225 | }); 226 | 227 | })(); 228 | -------------------------------------------------------------------------------- /src/models/GameMaster/index.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import POGOProtos from "pokemongo-protobuf"; 3 | 4 | import { 5 | idToPkmnBundleName 6 | } from "../../utils"; 7 | 8 | import CFG from "../../../cfg"; 9 | import ENUM from "../../enum"; 10 | 11 | import print from "../../print"; 12 | 13 | /** 14 | * @class GameMaster 15 | */ 16 | export default class GameMaster { 17 | 18 | /** 19 | * @param {GameServer} instance 20 | * @constructor 21 | */ 22 | constructor(instance) { 23 | 24 | this.instance = instance; 25 | 26 | this.settings = this.buildSettings(); 27 | 28 | this.decode = this.parse(); 29 | this.buffer = this.encode(); 30 | 31 | this.parseItemTemplates(); 32 | 33 | } 34 | 35 | /** 36 | * @return {Buffer} 37 | */ 38 | encode() { 39 | return ( 40 | POGOProtos.serialize(this.decode, "POGOProtos.Networking.Responses.DownloadItemTemplatesResponse") 41 | ); 42 | } 43 | 44 | parse() { 45 | let master = null; 46 | try { 47 | let data = fs.readFileSync(CFG.DUMP_ASSET_PATH + "game_master"); 48 | master = this.instance.parseProtobuf(data, "POGOProtos.Networking.Responses.DownloadItemTemplatesResponse"); 49 | } catch (e) { 50 | print(e, 31); 51 | } 52 | return (master); 53 | } 54 | 55 | parseItemTemplates() { 56 | 57 | let item = null; 58 | let items = this.decode.item_templates; 59 | 60 | let ii = 0; 61 | let length = items.length; 62 | 63 | for (; ii < length; ++ii) { 64 | item = items[ii]; 65 | this.parseKey(item, item.template_id); 66 | }; 67 | 68 | } 69 | 70 | /** 71 | * @param {Object} item 72 | * @param {String} key 73 | */ 74 | parseKey(item, key) { 75 | if (key in this.settings) { 76 | this.settings[key] = item; 77 | } 78 | } 79 | 80 | /** 81 | * @return {Object} 82 | */ 83 | buildSettings() { 84 | let settings = { 85 | "PLAYER_LEVEL_SETTINGS": null 86 | }; 87 | return (settings); 88 | } 89 | 90 | getPlayerSettings() { 91 | return ( 92 | this.settings["PLAYER_LEVEL_SETTINGS"].player_level 93 | ); 94 | } 95 | 96 | /** 97 | * @param {Number} dex 98 | * @return {Object} 99 | */ 100 | getPokemonTmplByDex(dex) { 101 | 102 | let id = idToPkmnBundleName(dex).substring(2); 103 | let name = ENUM.getNameById(ENUM.POKEMON_IDS, dex); 104 | let tmplId = `V${id}_POKEMON_${name}`; 105 | 106 | let item = null; 107 | let items = this.decode.item_templates; 108 | 109 | let ii = 0; 110 | let length = items.length; 111 | 112 | for (; ii < length; ++ii) { 113 | item = items[ii]; 114 | if ( 115 | item.pokemon_settings !== void 0 && 116 | item.template_id === tmplId 117 | ) { 118 | return (item.pokemon_settings); 119 | } 120 | }; 121 | 122 | return (null); 123 | 124 | } 125 | 126 | /** 127 | * @return {Buffer} 128 | */ 129 | serialize() { 130 | return ( 131 | this.buffer 132 | ); 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/models/Player/Avatar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Avatar 3 | */ 4 | export default class Avatar { 5 | 6 | /** 7 | * @param {Player} player 8 | * @constructor 9 | */ 10 | constructor(player) { 11 | 12 | this.player = player; 13 | 14 | this._skin = 0; 15 | this._hair = 0; 16 | this._shirt = 0; 17 | this._pants = 0; 18 | this._hat = 0; 19 | this._shoes = 0; 20 | this._eyes = 0; 21 | this._backpack = 0; 22 | this._gender = 0; 23 | 24 | } 25 | 26 | /** 27 | * @param {Number} value 28 | * @param {Number} a 29 | * @param {Number} b 30 | * @return {Boolean} 31 | */ 32 | between(value, a, b) { 33 | return ( 34 | value >= a && value <= b 35 | ); 36 | } 37 | 38 | // skin 39 | get skin() { 40 | return (this._skin); 41 | } 42 | set skin(value) { 43 | if (this.between(value, 0, 3)) { 44 | this._skin = value; 45 | } 46 | } 47 | 48 | // hair 49 | get hair() { 50 | return (this._hair); 51 | } 52 | set hair(value) { 53 | if (this.between(value, 0, 5)) { 54 | this._hair = value; 55 | } 56 | } 57 | 58 | // shirt 59 | get shirt() { 60 | return (this._shirt); 61 | } 62 | set shirt(value) { 63 | if (this.between(value, 0, 9)) { 64 | this._shirt = value; 65 | } 66 | } 67 | 68 | // pants 69 | get pants() { 70 | return (this._pants); 71 | } 72 | set pants(value) { 73 | if (this.between(value, 0, 5)) { 74 | this._pants = value; 75 | } 76 | } 77 | 78 | // hat 79 | get hat() { 80 | return (this._hat); 81 | } 82 | set hat(value) { 83 | if (this.between(value, 0, 4)) { 84 | this._hat = value; 85 | } 86 | } 87 | 88 | // shoes 89 | get shoes() { 90 | return (this._shoes); 91 | } 92 | set shoes(value) { 93 | if (this.between(value, 0, 6)) { 94 | this._shoes = value; 95 | } 96 | } 97 | 98 | // eyes 99 | get eyes() { 100 | return (this._eyes); 101 | } 102 | set eyes(value) { 103 | if (this.between(value, 0, 4)) { 104 | this._eyes = value; 105 | } 106 | } 107 | 108 | // backpack 109 | get backpack() { 110 | return (this._backpack); 111 | } 112 | set backpack(value) { 113 | if (this.between(value, 0, 5)) { 114 | this._backpack = value; 115 | } 116 | } 117 | 118 | // gender 119 | get gender() { 120 | return (this._gender === 0 ? "MALE" : "FEMALE"); 121 | } 122 | set gender(value) { 123 | if (this.between(value, 0, 1)) { 124 | this._gender = value; 125 | } 126 | } 127 | 128 | resetOutfit() { 129 | this.skin = 0; 130 | this.hair = 0; 131 | this.shirt = 0; 132 | this.pants = 0; 133 | this.hat = 0; 134 | this.shoes = 0; 135 | this.eyes = 0; 136 | this.backpack = 0; 137 | } 138 | 139 | /** 140 | * @return {Object} 141 | */ 142 | serialize() { 143 | return ({ 144 | skin: this.skin, 145 | hair: this.hair, 146 | shirt: this.shirt, 147 | pants: this.pants, 148 | hat: this.hat, 149 | shoes: this.shoes, 150 | eyes: this.eyes, 151 | gender: this.gender, 152 | backpack: this.backpack 153 | }); 154 | } 155 | 156 | /** 157 | * @return {String} 158 | */ 159 | querify() { 160 | return (JSON.stringify({ 161 | skin: this.skin, 162 | hair: this.hair, 163 | shirt: this.shirt, 164 | pants: this.pants, 165 | hat: this.hat, 166 | shoes: this.shoes, 167 | eyes: this.eyes, 168 | gender: this.gender, 169 | backpack: this.backpack 170 | })); 171 | } 172 | 173 | /** 174 | * @param {String} str 175 | */ 176 | parseJSON(str) { 177 | let obj = JSON.parse(str); 178 | for (let key in obj) { 179 | if (this.hasOwnProperty("_" + key)) { 180 | this[key] = obj[key] << 0; 181 | } 182 | }; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/models/Player/Bag/index.js: -------------------------------------------------------------------------------- 1 | import ENUM from "../../../enum"; 2 | 3 | /** 4 | * @class Bag 5 | */ 6 | export default class Bag { 7 | 8 | /** 9 | * @param {Player} player 10 | * @constructor 11 | */ 12 | constructor(player) { 13 | 14 | this.player = player; 15 | 16 | this.poke_ball = 0; 17 | this.great_ball = 0; 18 | this.ultra_ball = 0; 19 | this.master_ball = 0; 20 | 21 | this.potion = 0; 22 | this.super_potion = 0; 23 | this.hyper_potion = 0; 24 | this.max_potion = 0; 25 | 26 | this.revive = 0; 27 | this.max_revive = 0; 28 | 29 | this.lucky_egg = 0; 30 | this.troy_disk = 0; 31 | 32 | this.incense_ordinary = 0; 33 | this.incense_spicy = 0; 34 | this.incense_cool = 0; 35 | this.incense_floral = 0; 36 | 37 | this.razz_berry = 0; 38 | this.bluk_berry = 0; 39 | this.nanab_berry = 0; 40 | this.wepar_berry = 0; 41 | this.pinap_berry = 0; 42 | 43 | this.incubator_basic = 0; 44 | this.incubator_basic_unlimited = 0; 45 | 46 | this.pokemon_storage_upgrade = 0; 47 | this.storage_upgrade = 0; 48 | 49 | } 50 | 51 | /** 52 | * @param {String} name 53 | * @return {Number} 54 | */ 55 | getItemEnumId(name) { 56 | return ( 57 | ENUM.getIdByName(ENUM.ITEMS, name) 58 | ); 59 | } 60 | 61 | /** 62 | * @param {Number} id 63 | * @return {String} 64 | */ 65 | getItemEnumName(id) { 66 | return ( 67 | ENUM.getNameById(ENUM.ITEMS, id) 68 | ); 69 | } 70 | 71 | /** 72 | * @param {String} name 73 | * @return {String} 74 | */ 75 | getItemName(name) { 76 | return ( 77 | this.getItemEnumId("ITEM_" + name.toUpperCase()) 78 | ); 79 | } 80 | 81 | /** 82 | * @param {String} key 83 | * @return {String} 84 | */ 85 | getLocalItemKey(key) { 86 | return ( 87 | key.replace("ITEM_", "").toLowerCase() 88 | ); 89 | } 90 | 91 | /** 92 | * @param {String} key 93 | * @return {Number} 94 | */ 95 | getItemAmountByItemKey(key) { 96 | let name = this.getLocalItemKey(key); 97 | if (this.hasOwnProperty(name)) { 98 | return (this[name]); 99 | } 100 | return (-1); 101 | } 102 | 103 | /** 104 | * @param {String} name 105 | * @return {Boolean} 106 | */ 107 | isValidItemKey(name) { 108 | return ( 109 | this.hasOwnProperty(name) && Number.isInteger(this[name]) 110 | ); 111 | } 112 | 113 | /** 114 | * @param {String} name 115 | * @param {Number} amount 116 | * @return {Number} 117 | */ 118 | updateItem(name, amount) { 119 | let key = this.getLocalItemKey(name); 120 | if (!this.isValidItemKey(key)) return (-1); 121 | let currentAmount = this[key] << 0; 122 | if (amount < 0) { 123 | if (currentAmount + amount < 0) this[key] = 0; 124 | else this[key] += amount; 125 | } 126 | else { 127 | this[key] += amount; 128 | } 129 | return (this[key]); 130 | } 131 | 132 | /** 133 | * @return {Array} 134 | */ 135 | serialize() { 136 | let out = []; 137 | for (let key in this) { 138 | if (this.isValidItemKey(key)) { 139 | let itemId = this.getItemName(key); 140 | if (Number.isInteger(itemId) && this[key] > 0) { 141 | out.push({ 142 | modified_timestamp_ms: +new Date() - 1e3, 143 | inventory_item_data: { 144 | item: { 145 | item_id: "ITEM_" + key.toUpperCase(), 146 | count: this[key] << 0 147 | } 148 | } 149 | }); 150 | } 151 | } 152 | }; 153 | return (out); 154 | } 155 | 156 | /** 157 | * @return {String} 158 | */ 159 | querify() { 160 | let buffer = {}; 161 | for (let key in this) { 162 | if (this.isValidItemKey(key)) { 163 | let itemId = this.getItemName(key); 164 | if (Number.isInteger(itemId)) { 165 | buffer[itemId] = this[key]; 166 | } 167 | } 168 | }; 169 | return (JSON.stringify(buffer)); 170 | } 171 | 172 | /** 173 | * @param {String} str 174 | */ 175 | parseJSON(str) { 176 | let obj = JSON.parse(str); 177 | for (let key in obj) { 178 | let name = this.getItemEnumName(key).toLowerCase().replace("item_", ""); 179 | if (this.isValidItemKey(name)) { 180 | this[name] = obj[key]; 181 | } 182 | }; 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/models/Player/CandyBag/index.js: -------------------------------------------------------------------------------- 1 | import { GAME_MASTER } from "../../../shared"; 2 | 3 | import ENUM from "../../../enum"; 4 | 5 | /** 6 | * @class CandyBag 7 | */ 8 | export default class CandyBag { 9 | 10 | /** 11 | * @param {Player} player 12 | * @constructor 13 | */ 14 | constructor(player) { 15 | 16 | this.player = player; 17 | 18 | this.candies = {}; 19 | 20 | } 21 | 22 | /** 23 | * @param {Number} dex 24 | * @return {Object} 25 | */ 26 | getPkmnTemplate(dex) { 27 | let tmpl = GAME_MASTER.getPokemonTmplByDex(dex); 28 | return (tmpl); 29 | } 30 | 31 | /** 32 | * @param {Number} dex 33 | * @return {String} 34 | */ 35 | getPkmnFamily(dex) { 36 | return ( 37 | this.getPkmnTemplate(dex).family_id 38 | ); 39 | } 40 | 41 | /** 42 | * @param {Number} dex 43 | * @return {Object} 44 | */ 45 | createCandy(dex) { 46 | let id = ENUM.getIdByName(ENUM.POKEMON_FAMILY, this.getPkmnFamily(dex << 0)); 47 | let candy = { 48 | amount: 0 49 | }; 50 | this.candies[id] = candy; 51 | return (candy); 52 | } 53 | 54 | /** 55 | * @param {Number} dex 56 | * @return {Object} 57 | */ 58 | getCandyByDexNumber(dex) { 59 | let id = ENUM.getIdByName(ENUM.POKEMON_FAMILY, this.getPkmnFamily(dex << 0)); 60 | if (this.candies[id] !== void 0) { 61 | return (this.candies[id]); 62 | } 63 | else { 64 | return (this.createCandy(id) || null); 65 | } 66 | } 67 | 68 | /** 69 | * @param {Number} dex 70 | * @return {Number} 71 | */ 72 | getCandy(dex) { 73 | return ( 74 | this.getCandyByDexNumber(dex) 75 | ); 76 | } 77 | 78 | /** 79 | * @param {Number} dex 80 | * @param {Number} amount 81 | */ 82 | addCandy(dex, amount) { 83 | let candy = this.getCandyByDexNumber(dex); 84 | candy.amount += parseInt(amount); 85 | } 86 | 87 | /** 88 | * @param {Number} dex 89 | * @param {Number} amount 90 | */ 91 | removeCandy(dex, amount) { 92 | let candy = this.getCandyByDexNumber(dex); 93 | candy.amount -= parseInt(amount); 94 | if (candy.amount < 0) candy.amount = 0; 95 | } 96 | 97 | /** 98 | * @return {Array} 99 | */ 100 | serialize() { 101 | let out = []; 102 | for (let key in this.candies) { 103 | let candy = this.candies[key]; 104 | if (!(candy.amount > 0)) continue; 105 | out.push({ 106 | modified_timestamp_ms: +new Date() - 1e3, 107 | inventory_item_data: { 108 | candy: { 109 | family_id: this.getPkmnFamily(key << 0), 110 | candy: candy.amount 111 | } 112 | } 113 | }); 114 | }; 115 | return (out); 116 | } 117 | 118 | /** 119 | * @return {String} 120 | */ 121 | querify() { 122 | let buffer = {}; 123 | for (let key in this.candies) { 124 | let candy = this.candies[key].amount; 125 | buffer[key] = candy; 126 | }; 127 | return (JSON.stringify(buffer)); 128 | } 129 | 130 | /** 131 | * @param {String} str 132 | */ 133 | parseJSON(str) { 134 | let candies = JSON.parse(str); 135 | for (let candy in candies) { 136 | this.createCandy(candy).amount = parseInt(candies[candy]); 137 | }; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /src/models/Player/Contact/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Contact 3 | */ 4 | export default class Contact { 5 | 6 | /** 7 | * @param {Player} player 8 | * @constructor 9 | */ 10 | constructor(player) { 11 | 12 | this.player = player; 13 | 14 | this.sendMarketingEmails = false; 15 | this.sendPushNotifications = false; 16 | 17 | } 18 | 19 | /** 20 | * @return {Object} 21 | */ 22 | serialize() { 23 | return ({ 24 | send_marketing_emails: this.sendMarketingEmails, 25 | send_push_notifications: this.sendPushNotifications 26 | }); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/models/Player/Currency/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class Currency 3 | */ 4 | export default class Currency { 5 | 6 | /** 7 | * @param {Player} player 8 | * @constructor 9 | */ 10 | constructor(player) { 11 | 12 | this.player = player; 13 | 14 | } 15 | 16 | /** 17 | * @return {Array} 18 | */ 19 | serialize() { 20 | let out = []; 21 | out.push({ 22 | name: "POKECOIN", 23 | amount: this.player.info.pokecoin 24 | }); 25 | out.push({ 26 | name: "STARDUST", 27 | amount: this.player.info.stardust 28 | }); 29 | return (out); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/models/Player/Info/index.js: -------------------------------------------------------------------------------- 1 | import { GAME_MASTER } from "../../../shared"; 2 | 3 | import print from "../../../print"; 4 | 5 | import ENUM from "../../../enum"; 6 | 7 | /** 8 | * @class Info 9 | */ 10 | export default class Info { 11 | 12 | /** 13 | * @param {Player} player 14 | * @constructor 15 | */ 16 | constructor(player) { 17 | 18 | this.player = player; 19 | 20 | this.stardust = 0; 21 | this.pokecoins = 0; 22 | 23 | this._exp = 0; 24 | this._team = 0; 25 | this._level = 0; 26 | 27 | this.prevLvlExp = 0; 28 | this.nextLvlExp = 0; 29 | 30 | this.levelReward = false; 31 | 32 | this.kmWalked = 0; 33 | this.pkmnEncountered = 1; 34 | this.uniquePokedexEntries = 1; 35 | this.pkmnCaptured = 1; 36 | this.pokeStopVisits = 2; 37 | this.pokeballsThrown = 3; 38 | this.eggsHatched = 0; 39 | this.bigMagikarpCaught = 0; 40 | this.pkmnDeployed = 0; 41 | 42 | this.maxPkmnStorage = 250; 43 | this.maxItemStorage = 350; 44 | 45 | } 46 | 47 | get exp() { 48 | return (this._exp); 49 | } 50 | set exp(value) { 51 | this._exp = value; 52 | this.updatePrevNextExp(); 53 | } 54 | 55 | get level() { 56 | return (this._level); 57 | } 58 | set level(value) { 59 | this._level = value; 60 | this.updatePrevNextExp(); 61 | } 62 | 63 | get team() { 64 | return (ENUM.getNameById(ENUM.TEAM, this._team)); 65 | } 66 | set team(value) { 67 | this._team = value << 0; 68 | } 69 | 70 | updatePrevNextExp() { 71 | this.prevLvlExp = this.getLevelExp(this.level); 72 | this.nextLvlExp = this.getLevelExp(this.level + 1); 73 | } 74 | 75 | upgradeLevel() { 76 | 77 | } 78 | 79 | getLevelSettings() { 80 | return ( 81 | GAME_MASTER.settings.PLAYER_LEVEL_SETTINGS.player_level 82 | ); 83 | } 84 | 85 | getMaximumLevel() { 86 | return ( 87 | this.getLevelSettings().required_experience.length 88 | ); 89 | } 90 | 91 | getCurrentLevel() { 92 | let levels = this.getLevelSettings().required_experience; 93 | for (let key in levels) { 94 | if (levels[key] << 0 === this.nextLvlExp) { 95 | return (key << 0); 96 | } 97 | }; 98 | return (1); 99 | } 100 | 101 | getLevelExp(lvl) { 102 | return ( 103 | this.getLevelSettings().required_experience[lvl - 1] 104 | ); 105 | } 106 | 107 | /** 108 | * @param {Number} exp 109 | */ 110 | upgradeExp(exp) { 111 | if (!this.maxLevelReached()) { 112 | let currentLevelExp = this.getLevelExp(this.level); 113 | let nextLevelExp = this.getLevelExp(this.level + 1); 114 | let leftExp = nextLevelExp - this.exp; 115 | this.levelReward = false; 116 | if (this.exp + exp >= nextLevelExp) { 117 | this.level += 1; 118 | this.prevLvlExp = nextLevelExp; 119 | this.nextLvlExp = this.getLevelExp(this.level + 1); 120 | this.exp += leftExp + 1; 121 | this.levelReward = true; 122 | this.upgradeExp(exp - leftExp); 123 | } 124 | else { 125 | this.exp += exp; 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * @return {Boolean} 132 | */ 133 | maxLevelReached() { 134 | return ( 135 | this.level + 1 >= this.getMaximumLevel() 136 | ); 137 | } 138 | 139 | /** 140 | * @return {Object} 141 | */ 142 | serialize() { 143 | return ({ 144 | modified_timestamp_ms: +new Date(), 145 | inventory_item_data: { 146 | player_stats: { 147 | level: this.level, 148 | experience: this.exp, 149 | prev_level_xp: this.prevLvlExp, 150 | next_level_xp: this.nextLvlExp, 151 | km_walked: this.kmWalked, 152 | pokemons_encountered: this.pkmnEncountered, 153 | unique_pokedex_entries: this.uniquePokedexEntries, 154 | pokemons_captured: this.pkmnCaptured, 155 | poke_stop_visits: this.pokeStopVisits, 156 | pokeballs_thrown: this.pokeballsThrown, 157 | eggs_hatched: this.eggsHatched, 158 | big_magikarp_caught: this.bigMagikarpCaught, 159 | pokemon_deployed: this.pkmnDeployed 160 | } 161 | } 162 | }); 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /src/models/Player/Party/index.js: -------------------------------------------------------------------------------- 1 | import Pokemon from "../../Pokemon"; 2 | import WildPokemon from "../../Pokemon/WildPokemon"; 3 | 4 | import print from "../../../print"; 5 | 6 | import CFG from "../../../../cfg"; 7 | 8 | /** 9 | * @class Party 10 | */ 11 | export default class Party { 12 | 13 | /** 14 | * @param {Player} player 15 | * @constructor 16 | */ 17 | constructor(player) { 18 | 19 | this.player = player; 20 | 21 | this.party = []; 22 | 23 | } 24 | 25 | syncWithDatabase() { 26 | let query = `SELECT * FROM ${CFG.MYSQL_OWNED_PKMN_TABLE} WHERE owner_id=?`; 27 | return new Promise((resolve) => { 28 | this.player.world.db.query(query, [this.player.uid], (e, rows) => { 29 | if (e) return print(e, 31); 30 | rows.map((row) => { 31 | row.isOwned = true; 32 | this.addPkmn(row); 33 | }); 34 | resolve(); 35 | }); 36 | }); 37 | } 38 | 39 | /** 40 | * @param {Object} obj 41 | * @return {Pokemon} 42 | */ 43 | addPkmn(obj) { 44 | obj.owner = this.player; 45 | let pkmn = new Pokemon(obj); 46 | this.party.push(pkmn); 47 | return (pkmn); 48 | } 49 | 50 | /** 51 | * @param {Number} id 52 | * @return {Number} 53 | */ 54 | getPkmnIndexById(id) { 55 | id = parseInt(id); 56 | for (let ii = 0; ii < this.party.length; ++ii) { 57 | if (this.party[ii].uid === id) return (ii); 58 | }; 59 | return (-1); 60 | } 61 | 62 | /** 63 | * @param {Number} id 64 | * @return {Pokemon} 65 | */ 66 | getPkmnById(id) { 67 | let index = this.getPkmnIndexById(id); 68 | return (this.party[index]); 69 | } 70 | 71 | /** 72 | * @param {Number} id 73 | */ 74 | deletePkmn(id) { 75 | let index = this.getPkmnIndexById(id); 76 | let pkmn = this.party[index]; 77 | if (pkmn) this.party.splice(index, 1); 78 | } 79 | 80 | /** 81 | * @return {Number} 82 | */ 83 | getUniquePkmnCount() { 84 | let ii = 0; 85 | let dex = 0; 86 | let amount = 0; 87 | let length = this.party.length; 88 | let array = []; 89 | for (; ii < length; ++ii) { 90 | dex = this.party[ii].dexNumber; 91 | if (array.indexOf(dex) === -1) { 92 | array.push(dex); 93 | amount++; 94 | } 95 | }; 96 | return (amount); 97 | } 98 | 99 | /** 100 | * @return {Array} 101 | */ 102 | serialize() { 103 | let out = []; 104 | let ii = 0; 105 | let length = this.party.length; 106 | for (; ii < length; ++ii) { 107 | out.push({ 108 | "inventory_item_data": { 109 | "pokemon_data": this.party[ii].serialize() 110 | } 111 | }); 112 | }; 113 | return (out); 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /src/models/Player/PokeDex/index.js: -------------------------------------------------------------------------------- 1 | import { GAME_MASTER } from "../../../shared"; 2 | 3 | import ENUM from "../../../enum"; 4 | 5 | /** 6 | * @class PokeDex 7 | */ 8 | export default class PokeDex { 9 | 10 | /** 11 | * @param {Player} player 12 | * @constructor 13 | */ 14 | constructor(player) { 15 | 16 | this.player = player; 17 | 18 | this.pkmns = {}; 19 | 20 | } 21 | 22 | /** 23 | * @param {Number} dex 24 | * @return {Boolean} 25 | */ 26 | entryExists(dex) { 27 | return ( 28 | this.pkmns.hasOwnProperty(dex) 29 | ); 30 | } 31 | 32 | /** 33 | * @param {Number} dex 34 | * @param {Number} capture 35 | * @param {Number} encounter 36 | */ 37 | addEntry(dex, capture, encounter) { 38 | if (this.entryExists(dex)) { 39 | this.pkmns[dex].captured += capture << 0; 40 | this.pkmns[dex].encountered += encounter << 0; 41 | } else { 42 | this.pkmns[dex] = { 43 | captured: 0, 44 | encountered: 0 45 | }; 46 | this.addEntry(dex, capture, encounter); 47 | } 48 | } 49 | 50 | /** 51 | * @param {Number} dex 52 | */ 53 | removeEntry(dex) { 54 | if (this.entryExists(dex)) { 55 | delete this.pkmns[dex]; 56 | } 57 | } 58 | 59 | /** 60 | * @return {Array} 61 | */ 62 | serialize() { 63 | let out = []; 64 | for (let key in this.pkmns) { 65 | let pkmn = this.pkmns[key]; 66 | out.push({ 67 | modified_timestamp_ms: +new Date() - 1e3, 68 | inventory_item_data: { 69 | pokedex_entry: { 70 | pokemon_id: key, 71 | times_captured: this.pkmns[key].captured, 72 | times_encountered: this.pkmns[key].encountered 73 | } 74 | } 75 | }); 76 | }; 77 | return (out); 78 | } 79 | 80 | /** 81 | * @param {String} str 82 | */ 83 | parseJSON(str) { 84 | let pkmns = JSON.parse(str); 85 | for (let pkmn in pkmns) { 86 | this.addEntry(pkmn); 87 | }; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/models/Player/Tutorial/index.js: -------------------------------------------------------------------------------- 1 | import ENUM from "../../../enum"; 2 | 3 | /** 4 | * @class Tutorial 5 | */ 6 | export default class Tutorial { 7 | 8 | /** 9 | * @param {Player} player 10 | * @constructor 11 | */ 12 | constructor(player) { 13 | 14 | this.player = player; 15 | 16 | this.states = []; 17 | 18 | } 19 | 20 | /** 21 | * @param {String} name 22 | * @return {String} 23 | */ 24 | getItemName(name) { 25 | return ( 26 | ENUM.getNameById(ENUM.TUTORIAL, name) 27 | ); 28 | } 29 | 30 | skipTutorial() { 31 | this.states = [ 32 | "LEGAL_SCREEN", 33 | "AVATAR_SELECTION", 34 | "POKEMON_CAPTURE", 35 | "NAME_SELECTION", 36 | "FIRST_TIME_EXPERIENCE_COMPLETE" 37 | ]; 38 | } 39 | 40 | /** 41 | * @return {Array} 42 | */ 43 | serialize() { 44 | return ( 45 | this.states 46 | ); 47 | } 48 | 49 | /** 50 | * @return {String} 51 | */ 52 | querify() { 53 | let buffer = {}; 54 | for (let key in this.states) { 55 | let itemId = this.getItemName(key); 56 | buffer[itemId] = this[key]; 57 | }; 58 | return (JSON.stringify(buffer)); 59 | } 60 | 61 | /** 62 | * @param {String} str 63 | */ 64 | parseJSON(str) { 65 | this.states = []; 66 | let obj = JSON.parse(str); 67 | for (let key in obj) { 68 | let name = this.getItemName(key); 69 | if (obj[key] === 1) { 70 | this.states.push(name.toUpperCase()); 71 | } 72 | }; 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/models/Player/index.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import Bag from "./Bag"; 4 | import Info from "./Info"; 5 | import Party from "./Party"; 6 | import Avatar from "./Avatar"; 7 | import Pokedex from "./PokeDex"; 8 | import Contact from "./Contact"; 9 | import CandyBag from "./CandyBag"; 10 | import Tutorial from "./Tutorial"; 11 | import Currency from "./Currency"; 12 | 13 | import MapObject from "../World/MapObject"; 14 | 15 | import print from "../../print"; 16 | import CFG from "../../../cfg"; 17 | 18 | import * as _packets from "./packets"; 19 | 20 | import { 21 | inherit, 22 | parseSignature 23 | } from "../../utils"; 24 | 25 | import ENUM from "../../enum"; 26 | 27 | import { GAME_MASTER } from "../../shared"; 28 | 29 | /** 30 | * @class Player 31 | */ 32 | export default class Player extends MapObject { 33 | 34 | /** 35 | * @param {Object} obj 36 | * @constructor 37 | */ 38 | constructor(obj) { 39 | 40 | super(null); 41 | 42 | this.world = obj.world; 43 | 44 | this._email = null; 45 | 46 | this.username = "unknown"; 47 | 48 | this.email_verified = false; 49 | 50 | this.platform = null; 51 | 52 | this.isPTCAccount = false; 53 | this.isGoogleAccount = false; 54 | 55 | this.isIOS = false; 56 | this.isAndroid = false; 57 | 58 | this.hasSignature = false; 59 | 60 | this.authenticated = false; 61 | 62 | this.request = null; 63 | this.response = null; 64 | 65 | this.remoteAddress = null; 66 | 67 | this.currentEncounter = null; 68 | 69 | this.bag = new Bag(this); 70 | 71 | this.info = new Info(this); 72 | this.party = new Party(this); 73 | this.avatar = new Avatar(this); 74 | this.pokeDex = new Pokedex(this); 75 | this.contact = new Contact(this); 76 | this.candyBag = new CandyBag(this); 77 | this.tutorial = new Tutorial(this); 78 | this.currency = new Currency(this); 79 | 80 | this.refreshSocket(obj.request, obj.response); 81 | 82 | } 83 | 84 | get email() { 85 | return (this._email); 86 | } 87 | set email(value) { 88 | this._email = value; 89 | if (this.username === "unknown") this.username = value.replace("@gmail.com", ""); 90 | } 91 | 92 | /** 93 | * @param {Buffer} buffer 94 | */ 95 | sendResponse(buffer) { 96 | this.response.end(buffer); 97 | } 98 | 99 | /** 100 | * @param {Request} req 101 | * @param {String} type 102 | * @return {Boolean} 103 | */ 104 | requestContains(req, type) { 105 | let requests = req.requests; 106 | for (let request of requests) { 107 | if (request.request_type === type) return (true); 108 | }; 109 | return (false); 110 | } 111 | 112 | /** 113 | * @param {Request} req 114 | * @param {Response} res 115 | */ 116 | refreshSocket(req, res) { 117 | this.request = POGOProtos.parseWithUnknown(req.body, "POGOProtos.Networking.Envelopes.RequestEnvelope"); 118 | this.response = res; 119 | // Try to update players position on each req 120 | this.refreshPosition(); 121 | } 122 | 123 | refreshPosition() { 124 | let req = this.request; 125 | if ( 126 | req.latitude !== void 0 && 127 | req.longitude !== void 0 128 | ) { 129 | this.latitude = req.latitude; 130 | this.longitude = req.longitude; 131 | } 132 | if (this.requestContains(req, "GET_MAP_OBJECTS")) { 133 | this.world.triggerSpawnAt(this.latitude, this.longitude); 134 | } 135 | } 136 | 137 | getDevicePlatform() { 138 | let request = this.request; 139 | if (request.unknown6 && request.unknown6[0]) { 140 | let sig = parseSignature(request); 141 | if (sig.device_info !== void 0) { 142 | this.hasSignature = true; 143 | this.isIOS = sig.device_info.device_brand === "Apple"; 144 | this.isAndroid = !this.isIOS; 145 | this.platform = this.isIOS ? "ios" : "android"; 146 | print(`${this.email} is playing with an ${this.isIOS ? "Apple" : "Android"} device!`, 36); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * @param {String} type 153 | * @param {Object} msg 154 | */ 155 | getPacket(type, msg) { 156 | return new Promise((resolve) => { 157 | switch (type) { 158 | case "SET_FAVORITE_POKEMON": 159 | resolve(this.SetFavoritePokemon(msg)); 160 | break; 161 | case "LEVEL_UP_REWARDS": 162 | resolve(this.LevelUpRewards(msg)); 163 | break; 164 | case "RELEASE_POKEMON": 165 | this.ReleasePokemon(msg).then((result) => { 166 | resolve(result); 167 | }); 168 | break; 169 | case "UPGRADE_POKEMON": 170 | resolve(this.UpgradePokemon(msg)); 171 | break; 172 | case "GET_PLAYER_PROFILE": 173 | resolve(this.GetPlayerProfile(msg)); 174 | break; 175 | case "SET_AVATAR": 176 | resolve(this.SetAvatar(msg)); 177 | break; 178 | case "GET_PLAYER": 179 | resolve(this.GetPlayer(msg)); 180 | break; 181 | case "GET_INVENTORY": 182 | resolve(this.GetInventory(msg)); 183 | break; 184 | case "GET_ASSET_DIGEST": 185 | resolve(this.GetAssetDigest(msg)); 186 | break; 187 | case "NICKNAME_POKEMON": 188 | resolve(this.NicknamePokemon(msg)); 189 | break; 190 | case "GET_HATCHED_EGGS": 191 | resolve(this.GetHatchedEggs(msg)); 192 | break; 193 | case "CHECK_AWARDED_BADGES": 194 | resolve(this.CheckAwardedBadges(msg)); 195 | break; 196 | case "RECYCLE_INVENTORY_ITEM": 197 | resolve(this.RecycleInventoryItem(msg)); 198 | break; 199 | case "CLAIM_CODENAME": 200 | resolve(this.ClaimCodename(msg)); 201 | break; 202 | }; 203 | }); 204 | } 205 | 206 | /** 207 | * @return {Object} 208 | */ 209 | getPlayerData() { 210 | return ({ 211 | creation_timestamp_ms: +new Date(), 212 | username: this.username, 213 | team: this.info.team, 214 | tutorial_state: this.tutorial.serialize(), 215 | avatar: this.avatar.serialize(), 216 | max_pokemon_storage: 250, 217 | max_item_storage: 350, 218 | daily_bonus: {}, 219 | equipped_badge: {}, 220 | contact_settings: this.contact.serialize(), 221 | currencies: this.currency.serialize(), 222 | remaining_codename_claims: 10 223 | }); 224 | } 225 | 226 | inheritByObject(obj) { 227 | for (let key in obj) { 228 | // ignore 229 | if (!(key !== "email")) continue; 230 | if (key === "id") { 231 | this.uid = obj[key]; 232 | } 233 | else if (key === "candies") { 234 | this.candyBag.parseJSON(obj[key]); 235 | } 236 | else if (key === "items") { 237 | this.bag.parseJSON(obj[key]); 238 | } 239 | else if (key === "avatar") { 240 | this.avatar.parseJSON(obj[key]); 241 | } 242 | else if (key === "tutorial") { 243 | this.tutorial.parseJSON(obj[key]); 244 | } 245 | else if (key === "pokecoins") { 246 | this.info.pokecoins = obj[key] << 0; 247 | } 248 | else if (key === "stardust") { 249 | this.info.stardust = obj[key] << 0; 250 | } 251 | else if (key === "level") { 252 | this.info.level = obj[key] << 0; 253 | } 254 | else if (key === "exp") { 255 | this.info.exp = obj[key] << 0; 256 | } 257 | else if (key === "team") { 258 | this.info.team = obj[key] << 0; 259 | } 260 | else { 261 | if (this.hasOwnProperty(key)) { 262 | this[key] = obj[key]; 263 | } 264 | } 265 | }; 266 | } 267 | 268 | syncWithDatabase() { 269 | return new Promise((resolve) => { 270 | this.loadPlayerDatabase().then((row) => { 271 | this.party.syncWithDatabase().then(() => { 272 | resolve(); 273 | }); 274 | }); 275 | }); 276 | } 277 | 278 | loadPlayerDatabase() { 279 | let query = `SELECT * from ${CFG.MYSQL_USERS_TABLE} WHERE email=? LIMIT 1`; 280 | return new Promise((resolve) => { 281 | this.world.db.query(query, [this.email], (e, rows) => { 282 | if (e) return print(e, 31); 283 | if (rows.length >= 1) { 284 | this.inheritByObject(rows[0]); 285 | resolve(); 286 | } 287 | else print(`Failed to sync player ${this.username} with database!`, 31); 288 | }); 289 | }); 290 | } 291 | 292 | /** 293 | * @param {Fort} fort 294 | */ 295 | consumeFortRewards(fort) { 296 | let rewards = fort.rewards; 297 | for (let key in rewards) { 298 | let name = ENUM.getNameById(ENUM.ITEMS, key << 0).replace("ITEM_", "").toLowerCase(); 299 | if (this.bag.hasOwnProperty(name)) { 300 | this.bag[name] += rewards[key] << 0; 301 | } 302 | }; 303 | } 304 | 305 | /** 306 | * @param {WildPokemon} pkmn 307 | * @param {String} ball 308 | */ 309 | catchPkmn(pkmn, ball) { 310 | this.info.exp += 100; 311 | this.info.stardust += 100; 312 | this.info.pkmnCaptured += 1; 313 | this.currentEncounter = null; 314 | pkmn.caughtBy(this); 315 | pkmn.pokeball = ball; 316 | return new Promise((resolve) => { 317 | pkmn.owner = this; 318 | pkmn.insertIntoDatabase().then((insertId) => { 319 | let cp = pkmn.getSeenCp(this); 320 | pkmn.isOwned = false; 321 | pkmn = this.party.addPkmn(pkmn); 322 | pkmn.isWild = false; 323 | pkmn.isOwned = true; 324 | pkmn.cp = cp; 325 | pkmn.uid = insertId; 326 | pkmn.addCandies(3); 327 | print(`${this.username} caught a wild ${pkmn.getPkmnName()}!`); 328 | resolve({ 329 | status: "CATCH_SUCCESS", 330 | captured_pokemon_id: pkmn.uid, 331 | capture_award: { 332 | activity_type: ["ACTIVITY_CATCH_POKEMON"], 333 | xp: [100], 334 | candy: [3], 335 | stardust: [100] 336 | } 337 | }); 338 | }); 339 | }); 340 | } 341 | 342 | /** 343 | * @param {WildPokemon} pkmn 344 | */ 345 | releasePkmn(pkmn) { 346 | pkmn.addCandies(3); 347 | this.party.deletePkmn(pkmn.uid); 348 | return new Promise((resolve) => { 349 | pkmn.deleteFromDatabase().then(() => { 350 | resolve(); 351 | }); 352 | }); 353 | } 354 | 355 | /** 356 | * @param {string} codename 357 | */ 358 | setUsername(codename) { 359 | this.username = codename; 360 | } 361 | 362 | } 363 | 364 | inherit(Player, _packets); 365 | -------------------------------------------------------------------------------- /src/models/Player/packets/CheckAwardedBadges.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function CheckAwardedBadges(msg) { 8 | 9 | let buffer = { 10 | success: true, 11 | awarded_badges: [], 12 | awarded_badge_levels: [] 13 | }; 14 | 15 | return ( 16 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.CheckAwardedBadgesResponse") 17 | ); 18 | 19 | } -------------------------------------------------------------------------------- /src/models/Player/packets/ClaimCodename.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function ClaimCodename(msg) { 8 | 9 | let buffer = null; 10 | let schema = "POGOProtos.Networking.Responses.ClaimCodenameResponse"; 11 | 12 | let codename = msg.codename; 13 | if(codename) this.setUsername(codename); 14 | 15 | buffer = { 16 | codename: codename, 17 | "user_message": "Testing message", 18 | "is_assignable": true, 19 | status: null, 20 | updated_player: null 21 | }; 22 | 23 | buffer.status = codename ? "SUCCESS" : "CODENAME_NOT_AVAILABLE"; 24 | buffer.updated_player = this.getPlayerData(); 25 | 26 | 27 | return (POGOProtos.serialize(buffer, schema)); 28 | 29 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetAssetDigest.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import * as shared from "../../../shared"; 4 | 5 | /** 6 | * @param {Object} msg 7 | * @return {Buffer} 8 | */ 9 | export default function GetAssetDigest(msg) { 10 | return ( 11 | shared.GAME_ASSETS[this.platform].buffer 12 | ); 13 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetAuthTicket.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | export default function GetAuthTicket(id) { 4 | 5 | let buffer = ({ 6 | status_code: 53, 7 | request_id: id, 8 | api_url: "pgorelease.nianticlabs.com/custom", 9 | auth_ticket: { 10 | start: new Buffer(""), 11 | expire_timestamp_ms: ((new Date).getTime() + (1000 * 60 * 30)), 12 | end: new Buffer("") 13 | } 14 | }); 15 | 16 | return ( 17 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Envelopes.ResponseEnvelope") 18 | ); 19 | 20 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetHatchedEggs.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function GetHatchedEggs(msg) { 8 | 9 | let buffer = { 10 | success: true, 11 | pokemon_id: [], 12 | experience_awarded: [], 13 | candy_awarded: [], 14 | stardust_awarded: [] 15 | }; 16 | 17 | return ( 18 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetHatchedEggsResponse") 19 | ); 20 | 21 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetInventory.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function GetInventory(msg) { 8 | 9 | let items = this.bag.serialize(); 10 | let stats = this.info.serialize(); 11 | let party = this.party.serialize(); 12 | let candies = this.candyBag.serialize(); 13 | let currencies = this.currency.serialize(); 14 | //let pokedex = this.pokedex.serialize(); 15 | 16 | items.push(stats); 17 | items.push(candies); 18 | items.push(currencies); 19 | 20 | candies.map((candy) => { 21 | items.push(candy); 22 | }); 23 | 24 | party.map((pkmn) => { 25 | items.push(pkmn); 26 | }); 27 | 28 | let buffer = { 29 | success: true, 30 | inventory_delta: { 31 | new_timestamp_ms: +new Date(), 32 | inventory_items: items 33 | } 34 | }; 35 | 36 | return ( 37 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetInventoryResponse") 38 | ); 39 | 40 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetPlayer.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import ENUM from "../../../enum"; 4 | 5 | /** 6 | * @param {Object} msg 7 | * @return {Buffer} 8 | */ 9 | export default function GetPlayer(msg) { 10 | 11 | let buffer = { 12 | success: true, 13 | player_data: this.getPlayerData() 14 | }; 15 | 16 | return ( 17 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetPlayerResponse") 18 | ); 19 | 20 | } -------------------------------------------------------------------------------- /src/models/Player/packets/GetPlayerProfile.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | export default function GetPlayerProfile() { 4 | 5 | let buffer = ({ 6 | result: "SUCCESS", 7 | start_time: +new Date(), 8 | badges: [ 9 | { 10 | badge_type: "BADGE_TRAVEL_KM", 11 | end_value: 2674, 12 | current_value: 1337 13 | } 14 | ] 15 | }); 16 | 17 | return ( 18 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetPlayerProfileResponse") 19 | ); 20 | 21 | } -------------------------------------------------------------------------------- /src/models/Player/packets/LevelUpRewards.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function LevelUpRewards(msg) { 8 | 9 | let buffer = { 10 | result: "SUCCESS", 11 | items_awarded: [ 12 | { 13 | item_id: "ITEM_POKE_BALL", 14 | item_count: 2 15 | }, 16 | { 17 | item_id: "ITEM_TROY_DISK", 18 | item_count: 2 19 | } 20 | ], 21 | items_unlocked: [] 22 | }; 23 | 24 | return ( 25 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.LevelUpRewardsResponse") 26 | ); 27 | 28 | } -------------------------------------------------------------------------------- /src/models/Player/packets/NicknamePokemon.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function NicknamePokemon(msg) { 8 | 9 | let buffer = null; 10 | let schema = "POGOProtos.Networking.Responses.NicknamePokemonResponse"; 11 | 12 | let pkmn = this.party.getPkmnById(msg.pokemon_id); 13 | 14 | buffer = { result: null }; 15 | 16 | if (pkmn) pkmn.setNickname(String(msg.nickname)); 17 | buffer.result = pkmn ? "SUCCESS" : "ERROR_INVALID_NICKNAME"; 18 | 19 | return (POGOProtos.serialize(buffer, schema)); 20 | 21 | } -------------------------------------------------------------------------------- /src/models/Player/packets/RecycleInventoryItem.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function RecycleInventoryItem(msg) { 8 | 9 | let buffer = null; 10 | 11 | let success = this.bag.updateItem(msg.item_id, -(msg.count << 0)); 12 | 13 | buffer = { 14 | result: success !== -1 ? "SUCCESS" : "ERROR_NOT_ENOUGH_COPIES", 15 | new_count: success, 16 | }; 17 | 18 | return ( 19 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.RecycleInventoryItemResponse") 20 | ); 21 | 22 | } -------------------------------------------------------------------------------- /src/models/Player/packets/ReleasePokemon.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function ReleasePokemon(msg) { 8 | 9 | let buffer = null; 10 | let schema = "POGOProtos.Networking.Responses.ReleasePokemonResponse"; 11 | 12 | let pkmn = this.party.getPkmnById(msg.pokemon_id); 13 | 14 | return new Promise((resolve) => { 15 | if (pkmn) { 16 | this.releasePkmn(pkmn).then((result) => { 17 | buffer = { 18 | result: "SUCCESS", 19 | candy_awarded: 3 20 | }; 21 | resolve(POGOProtos.serialize(buffer, schema)); 22 | }); 23 | } else { 24 | buffer = { 25 | result: "FAILED" 26 | }; 27 | resolve(POGOProtos.serialize(buffer, schema)); 28 | } 29 | }); 30 | 31 | } -------------------------------------------------------------------------------- /src/models/Player/packets/SetAvatar.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function SetAvatar(msg) { 8 | 9 | if (!msg.player_avatar.hasOwnProperty("gender")) { 10 | msg.player_avatar.gender = "MALE"; 11 | } 12 | 13 | let gender = msg.player_avatar.gender; 14 | let genderChange = this.avatar.gender !== gender; 15 | 16 | msg.player_avatar.gender = gender === "MALE" ? 0 : 1; 17 | 18 | if (genderChange) { 19 | this.avatar.resetOutfit(); 20 | } 21 | 22 | for (let key in msg.player_avatar) { 23 | this.avatar[key] = msg.player_avatar[key]; 24 | }; 25 | 26 | let buffer = ({ 27 | status: "SUCCESS", 28 | player_data: this.getPlayerData() 29 | }); 30 | 31 | return ( 32 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.SetAvatarResponse") 33 | ); 34 | 35 | } -------------------------------------------------------------------------------- /src/models/Player/packets/SetFavoritePokemon.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function SetFavoritePokemon(msg) { 8 | 9 | let buffer = null; 10 | let pkmn = this.party.getPkmnById(msg.pokemon_id); 11 | 12 | if (pkmn) { 13 | pkmn.setFavorite(msg.is_favorite); 14 | buffer = { result: "SUCCESS" }; 15 | } 16 | else { 17 | buffer = { result: "ERROR_POKEMON_NOT_FOUND" }; 18 | } 19 | 20 | return ( 21 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.SetFavoritePokemonResponse") 22 | ); 23 | 24 | } -------------------------------------------------------------------------------- /src/models/Player/packets/UpgradePokemon.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | * @return {Buffer} 6 | */ 7 | export default function UpgradePokemon(msg) { 8 | 9 | let buffer = null; 10 | let pkmn = this.party.getPkmnById(msg.pokemon_id); 11 | 12 | if (pkmn) { 13 | if (pkmn.powerUp()) { 14 | buffer = { result: "SUCCESS" }; 15 | } else { 16 | buffer = { result: "ERROR_INSUFFICIENT_RESOURCES" }; 17 | } 18 | buffer.upgraded_pokemon = pkmn.serialize(); 19 | } 20 | else { 21 | buffer = { result: "ERROR_POKEMON_NOT_FOUND" }; 22 | } 23 | 24 | return ( 25 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.UpgradePokemonResponse") 26 | ); 27 | 28 | } -------------------------------------------------------------------------------- /src/models/Player/packets/index.js: -------------------------------------------------------------------------------- 1 | export SetAvatar from "./SetAvatar"; 2 | export GetPlayer from "./GetPlayer"; 3 | export GetInventory from "./GetInventory"; 4 | export GetAuthTicket from "./GetAuthTicket"; 5 | export ReleasePokemon from "./ReleasePokemon"; 6 | export UpgradePokemon from "./UpgradePokemon"; 7 | export LevelUpRewards from "./LevelUpRewards"; 8 | export GetHatchedEggs from "./GetHatchedEggs"; 9 | export GetAssetDigest from "./GetAssetDigest"; 10 | export NicknamePokemon from "./NicknamePokemon"; 11 | export GetPlayerProfile from "./GetPlayerProfile"; 12 | export SetFavoritePokemon from "./SetFavoritePokemon"; 13 | export CheckAwardedBadges from "./CheckAwardedBadges"; 14 | export RecycleInventoryItem from "./RecycleInventoryItem"; 15 | export ClaimCodename from "./ClaimCodename"; -------------------------------------------------------------------------------- /src/models/Pokemon/WildPokemon/index.js: -------------------------------------------------------------------------------- 1 | import Pokemon from "../index"; 2 | 3 | import { 4 | getUniqueHash, 5 | getHashCodeFrom 6 | } from "../../../utils"; 7 | 8 | /** 9 | * @class WildPokemon 10 | */ 11 | export default class WildPokemon extends Pokemon { 12 | 13 | /** 14 | * @param {Object} obj 15 | * @constructor 16 | */ 17 | constructor(obj) { 18 | 19 | super(obj); 20 | 21 | this.uid = getUniqueHash(); 22 | 23 | this.encounterId = this.getEncounterId(); 24 | 25 | this.despawnIn = -1; 26 | this.isDespawned = false; 27 | 28 | this.minExpire = obj.minExpire; 29 | this.maxExpire = obj.maxExpire; 30 | 31 | this.creation = +new Date(); 32 | 33 | this.expiration = (Math.floor((Math.random() * this.maxExpire) + this.minExpire) * 1e3); 34 | 35 | // players who already caught this pkmn 36 | this.hasCatched = []; 37 | 38 | // players who already have seen this pkmn (to store cp) 39 | this.hasSeen = []; 40 | 41 | } 42 | 43 | /** 44 | * @param {Player} player 45 | * @return {Boolean} 46 | */ 47 | caughtBy(player) { 48 | if (!this.alreadyCatchedBy(player)) { 49 | this.hasCatched.push(player.uid); 50 | } 51 | } 52 | 53 | /** 54 | * @param {Player} player 55 | * @return {Boolean} 56 | */ 57 | alreadyCatchedBy(player) { 58 | return ( 59 | this.hasCatched.indexOf(player.uid) > -1 60 | ); 61 | } 62 | 63 | /** 64 | * @param {Player} player 65 | * @return {Number} 66 | */ 67 | getSeenCp(player) { 68 | for (let item of this.hasSeen) { 69 | if (item.uid === player.uid) return (item.cp); 70 | }; 71 | return (-1); 72 | } 73 | 74 | /** 75 | * @param {Player} player 76 | * @return {Boolean} 77 | */ 78 | seenBy(player) { 79 | if (!this.alreadySeenBy(player)) { 80 | this.calcStats(player); 81 | this.hasSeen.push({ 82 | cp: this.cp, 83 | uid: player.uid 84 | }); 85 | } 86 | } 87 | 88 | /** 89 | * @param {Player} player 90 | * @return {Boolean} 91 | */ 92 | alreadySeenBy(player) { 93 | for (let item of this.hasSeen) { 94 | if (item.uid === player.uid) return (true); 95 | }; 96 | return (false); 97 | } 98 | 99 | /** 100 | * @return {Boolean} 101 | */ 102 | isExpired() { 103 | return ( 104 | +new Date() - this.creation >= this.expiration 105 | ); 106 | } 107 | 108 | /** 109 | * @return {Number} 110 | */ 111 | getEncounterId() { 112 | return (this.uid); 113 | } 114 | 115 | /** 116 | * @return {String} 117 | */ 118 | getPkmnId() { 119 | return ( 120 | this.getPkmnName().toUpperCase() 121 | ); 122 | } 123 | 124 | /** 125 | * @return {Object} 126 | */ 127 | serializeWild() { 128 | return ({ 129 | encounter_id: this.encounterId, 130 | last_modified_timestamp_ms: this.creation, 131 | latitude: this.latitude, 132 | longitude: this.longitude, 133 | spawn_point_id: this.spawnPointId, 134 | pokemon_data: { 135 | pokemon_id: this.getPkmnId(), 136 | cp: this.cp, 137 | stamina: 10, 138 | stamina_max: 10, 139 | move_1: "BUG_BITE_FAST", 140 | move_2: "STRUGGLE", 141 | height_m: 0.30962005257606506, 142 | weight_kg: 3.3212273120880127, 143 | individual_attack: 7, 144 | individual_defense: 13, 145 | individual_stamina: 3, 146 | cp_multiplier: 0.16639786958694458 147 | }, 148 | time_till_hidden_ms: this.expiration 149 | }); 150 | } 151 | 152 | /** 153 | * @return {Object} 154 | */ 155 | serializeCatchable() { 156 | return ({ 157 | encounter_id: this.encounterId, 158 | pokemon_id: this.getPkmnId(), 159 | expiration_timestamp_ms: this.creation + this.expiration, 160 | latitude: this.latitude, 161 | longitude: this.longitude 162 | }); 163 | } 164 | 165 | /** 166 | * @return {Object} 167 | */ 168 | serializeNearby() { 169 | return ({ 170 | pokemon_id: this.getPkmnId(), 171 | encounter_id: this.getEncounterId() 172 | }); 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /src/models/Pokemon/action.js: -------------------------------------------------------------------------------- 1 | import print from "../../print"; 2 | 3 | import { 4 | _toCC, 5 | } from "../../utils"; 6 | 7 | const pokename = require("pokename")(); 8 | 9 | /** 10 | * @return {Boolean} 11 | */ 12 | export function powerUp() { 13 | if (this.hasReachedMaxLevel()) { 14 | print(`${this.owner.username}'s ${this.getPkmnName()} already reached maximum level!`); 15 | return void 0; 16 | } 17 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 18 | let ownerStardust = this.owner.info.stardust; 19 | let ownerPkmnCandies = this.owner.candyBag.getCandy(this.dexNumber); 20 | let requiredCandies = 1; 21 | print(`${this.getPkmnName()} requires ${requiredCandies} candies to power up!`); 22 | if (ownerPkmnCandies >= requiredCandies) { 23 | this.level += 1; 24 | this.owner.candyBag.removeCandy(this.dexNumber, requiredCandies); 25 | return (true); 26 | } 27 | return (false); 28 | } 29 | 30 | /** 31 | * @return {Boolean} 32 | */ 33 | export function evolve() { 34 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 35 | let ownerPkmnCandies = this.owner.candyBag.getCandy(this.dexNumber); 36 | let candiesToEvolve = this.candiesToEvolve(); 37 | if (ownerPkmnCandies < candiesToEvolve()) { 38 | return print(`You have ${ownerPkmnCandies}/${candiesToEvolve} candies to evolve ${this.getPkmnName()}!`, 31); 39 | } 40 | let evolutions = pkmnTmpl.evolution_ids; 41 | if (this.hasEvolution() && evolutions.length <= 1) { 42 | let result = this.evolveInto(evolutions[0]); 43 | this.owner.candyBag.removeCandy(this.dexNumber, pkmnTmpl.candy_to_evolve); 44 | return (result); 45 | } 46 | else { 47 | print(`Evolving this pokemon isnt supported yet!`, 31); 48 | } 49 | return (false); 50 | } 51 | 52 | /** 53 | * @param {String} ev 54 | * @return {Boolean} 55 | */ 56 | export function evolveInto(ev) { 57 | let evName = _toCC(ev); 58 | let evId = pokename.getPokemonIdByName(evName); 59 | if (evId <= 0) { 60 | print(`Failed at retrieving id for pokemon ${ev}`, 31); 61 | return (false); 62 | } 63 | let evTmpl = this.getPkmnTemplate(evId); 64 | print(`${this.owner.username} successfully evolved ${this.getPkmnName()} into ${evName}`); 65 | return (true); 66 | } -------------------------------------------------------------------------------- /src/models/Pokemon/calc.js: -------------------------------------------------------------------------------- 1 | import Settings from "../../modes"; 2 | 3 | /** 4 | * @param {Player} owner 5 | */ 6 | export function calcStats(owner) { 7 | 8 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 9 | let stats = pkmnTmpl.stats; 10 | 11 | let minIV = Settings.PKMN_SETTINGS.MIN_IV; 12 | let maxIV = Settings.PKMN_SETTINGS.MAX_IV; 13 | 14 | this.attack = stats.base_attack; 15 | this.defense = stats.base_defense; 16 | this.stamina = stats.base_stamina; 17 | this.staminaMax = this.stamina; 18 | 19 | this.ivAttack = ~~(Math.random() * maxIV) + minIV; 20 | this.ivDefense = ~~(Math.random() * maxIV) + minIV; 21 | this.ivStamina = ~~(Math.random() * maxIV) + minIV; 22 | 23 | this.height = pkmnTmpl.pokedex_height_m + ((Math.random() * pkmnTmpl.height_std_dev) + .1); 24 | this.weight = pkmnTmpl.pokedex_weight_kg + ((Math.random() * pkmnTmpl.weight_std_dev) + .1); 25 | 26 | if (owner !== null) { 27 | this.cp = Math.floor(Math.random() * this.calcCP(owner)) + 16; 28 | } 29 | 30 | this.calcMoves(); 31 | 32 | } 33 | 34 | export function calcMoves() { 35 | 36 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 37 | 38 | let weakMoves = pkmnTmpl.quick_moves; 39 | let strongMoves = pkmnTmpl.cinematic_moves; 40 | 41 | this.move1 = weakMoves[(Math.random() * weakMoves.length) << 0]; 42 | this.move2 = strongMoves[(Math.random() * strongMoves.length) << 0]; 43 | 44 | } 45 | 46 | /** 47 | * @param {Player} owner 48 | * @return {Number} 49 | */ 50 | export function calcCP(owner) { 51 | 52 | let levelSettings = owner.info.getLevelSettings(); 53 | let ecpm = levelSettings.cp_multiplier[owner.info.level - 1]; 54 | 55 | let atk = (this.attack + this.ivAttack) * ecpm; 56 | let def = (this.defense + this.ivDefense) * ecpm; 57 | let sta = (this.stamina + this.ivStamina) * ecpm; 58 | 59 | return ( 60 | Math.max(10, Math.floor(Math.sqrt(atk * atk * def * sta) / 10)) 61 | ); 62 | 63 | } 64 | 65 | /** 66 | * @param {Number} lvl 67 | * @return {Number} 68 | */ 69 | export function getHalfLevelCPMultiplier(lvl) { 70 | let next = this.getCPMultipliers()[lvl + 1]; 71 | return ( 72 | Math.sqrt(Math.pow(lvl, 2) + ((Math.pow(next, 2) - Math.pow(lvl, 2)) / 2)) 73 | ); 74 | } -------------------------------------------------------------------------------- /src/models/Pokemon/index.js: -------------------------------------------------------------------------------- 1 | import MapObject from "../World/MapObject"; 2 | 3 | import { GAME_MASTER } from "../../shared"; 4 | 5 | import Settings from "../../modes"; 6 | 7 | import { 8 | _toCC, 9 | inherit, 10 | deCapitalize, 11 | validUsername 12 | } from "../../utils"; 13 | 14 | import print from "../../print"; 15 | 16 | import CFG from "../../../cfg"; 17 | import ENUM from "../../enum"; 18 | 19 | import * as _calc from "./calc"; 20 | import * as _actions from "./action"; 21 | 22 | const pokename = require("pokename")(); 23 | 24 | /** 25 | * @class Pokemon 26 | */ 27 | export default class Pokemon extends MapObject { 28 | 29 | /** 30 | * @param {Object} obj 31 | * @constructor 32 | */ 33 | constructor(obj) { 34 | 35 | super(null); 36 | 37 | this.dexNumber = 0; 38 | 39 | this._level = 1; 40 | this.capturedLevel = 0; 41 | 42 | this.cp = 0; 43 | this.cpMultiplier = Math.random() + 1.0; 44 | this.addCpMultiplier = 0; 45 | 46 | this.move1 = 0; 47 | this.move2 = 0; 48 | 49 | this.attack = 0; 50 | this.defense = 0; 51 | this.stamina = 0; 52 | 53 | this.height = 0; 54 | this.weight = 0; 55 | 56 | this.ivAttack = 0; 57 | this.ivDefense = 0; 58 | this.ivStamina = 0; 59 | 60 | this.staminaMax = 0; 61 | 62 | this.favorite = 0; 63 | 64 | this.owner = null; 65 | this.nickname = null; 66 | this.pokeball = null; 67 | 68 | this.isWild = false; 69 | this.isOwned = false; 70 | 71 | this.spawnPoint = null; 72 | 73 | this.init(obj); 74 | 75 | } 76 | 77 | get level() { 78 | return (this._level + 1); 79 | } 80 | set level(value) { 81 | this._level = parseFloat(value); 82 | } 83 | 84 | /** 85 | * @param {Object} obj 86 | */ 87 | init(obj) { 88 | obj = obj || {}; 89 | for (let key in obj) { 90 | if (this.hasOwnProperty(key)) { 91 | this[key] = obj[key]; 92 | } 93 | else if (this.hasOwnProperty(this.normalizeKey(key))) { 94 | this[this.normalizeKey(key)] = obj[key]; 95 | } 96 | else if (key === "id") { 97 | this.uid = parseInt(obj[key]); 98 | } 99 | else if (key === "move_1") { 100 | this.move1 = obj[key]; 101 | } 102 | else if (key === "move_2") { 103 | this.move2 = obj[key]; 104 | } 105 | else if (key === "height_m") { 106 | this.height = obj[key]; 107 | } 108 | else if (key === "weight_kg") { 109 | this.weight = obj[key]; 110 | } 111 | else if (key === "individual_attack") { 112 | this.ivAttack = obj[key]; 113 | } 114 | else if (key === "individual_defense") { 115 | this.ivDefense = obj[key]; 116 | } 117 | else if (key === "individual_stamina") { 118 | this.ivStamina = obj[key]; 119 | } 120 | }; 121 | if (!obj.isWild && !obj.isOwned) this.calcStats(this.owner); 122 | } 123 | 124 | /** 125 | * @param {String} key 126 | * @return {String} 127 | */ 128 | normalizeKey(key) { 129 | return ( 130 | deCapitalize(_toCC(key)) 131 | ); 132 | } 133 | 134 | /** 135 | * @param {Boolean} truth 136 | */ 137 | setFavorite(truth) { 138 | this.favorite = !!truth; 139 | } 140 | 141 | /** 142 | * @param {String} name 143 | */ 144 | setNickname(name) { 145 | if (validUsername(name)) { 146 | this.nickname = name; 147 | } 148 | } 149 | 150 | /** 151 | * @param {Number} dex 152 | * @return {Object} 153 | */ 154 | getPkmnTemplate(dex) { 155 | let tmpl = GAME_MASTER.getPokemonTmplByDex(dex); 156 | return (tmpl); 157 | } 158 | 159 | /** 160 | * @return {String} 161 | */ 162 | getPkmnName() { 163 | return ( 164 | pokename.getPokemonNameById(this.dexNumber) 165 | ); 166 | } 167 | 168 | /** 169 | * @param {Number} dex 170 | * @return {String} 171 | */ 172 | getPkmnFamily(dex) { 173 | return ( 174 | this.getPkmnTemplate(dex).family_id 175 | ); 176 | } 177 | 178 | /** 179 | * @param {Number} amount 180 | */ 181 | addCandies(amount) { 182 | let family = this.getPkmnFamily(this.dexNumber); 183 | let id = ENUM.getIdByName(ENUM.POKEMON_FAMILY, family) << 0; 184 | if (this.owner) this.owner.candyBag.addCandy(id, parseInt(amount)); 185 | } 186 | 187 | /** 188 | * @return {Boolean} 189 | */ 190 | hasEvolution() { 191 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 192 | return ( 193 | pkmnTmpl.evolution_ids.length >= 1 194 | ); 195 | } 196 | 197 | /** 198 | * @return {Number} 199 | */ 200 | candiesToEvolve() { 201 | let pkmnTmpl = this.getPkmnTemplate(this.dexNumber); 202 | return (pkmnTmpl.candy_to_evolve << 0); 203 | } 204 | 205 | /** 206 | * @return {Boolean} 207 | */ 208 | hasReachedMaxLevel() { 209 | return ( 210 | this.level > this.owner.info.getMaximumLevel() * 2 211 | ); 212 | } 213 | 214 | insertIntoDatabase() { 215 | let query = ` 216 | INSERT INTO ${CFG.MYSQL_OWNED_PKMN_TABLE} SET 217 | owner_id=?, 218 | dex_number=?, 219 | cp=?, 220 | stamina=?, 221 | stamina_max=?, 222 | move_1=?, 223 | move_2=?, 224 | height_m=?, 225 | weight_kg=?, 226 | individual_attack=?, 227 | individual_defense=?, 228 | individual_stamina=?, 229 | cp_multiplier=?, 230 | pokeball=?, 231 | favorite=?, 232 | nickname=? 233 | `; 234 | let data = [ 235 | this.owner.uid, this.dexNumber, this.cp, 236 | this.stamina, this.staminaMax, 237 | this.move1, this.move2, 238 | this.height, this.weight, 239 | this.ivAttack, this.ivDefense, this.ivStamina, 240 | this.cpMultiplier, this.pokeball, this.favorite, this.nickname || "" 241 | ]; 242 | return new Promise((resolve) => { 243 | this.owner.world.db.query(query, data, (e, res) => { 244 | if (e) return print(e, 31); 245 | resolve(res.insertId); 246 | }); 247 | }); 248 | } 249 | 250 | deleteFromDatabase() { 251 | let query = `DELETE FROM ${CFG.MYSQL_OWNED_PKMN_TABLE} WHERE id=? AND owner_id=? LIMIT 1`; 252 | return new Promise((resolve) => { 253 | this.owner.world.db.query(query, [this.uid, this.owner.uid], (e, res) => { 254 | if (e) return print(e, 31); 255 | resolve(res); 256 | }); 257 | }); 258 | } 259 | 260 | /** 261 | * @return {Object} 262 | */ 263 | serialize() { 264 | return ({ 265 | id: this.uid, 266 | pokemon_id: this.dexNumber, 267 | cp: this.cp, 268 | stamina: this.stamina, 269 | stamina_max: this.staminaMax, 270 | move_1: this.move1, 271 | move_2: this.move2, 272 | height_m: this.height, 273 | weight_kg: this.weight, 274 | individual_attack: this.ivAttack, 275 | individual_defense: this.ivDefense, 276 | individual_stamina: this.ivStamina, 277 | cp_multiplier: this.cpMultiplier, 278 | pokeball: "ITEM_POKE_BALL", 279 | captured_cell_id: "1337", 280 | creation_time_ms: +new Date(), 281 | favorite: this.favorite, 282 | nickname: this.nickname 283 | }); 284 | } 285 | 286 | /** 287 | * @return {Array} 288 | */ 289 | querify() { 290 | return ([ 291 | 292 | ]); 293 | } 294 | 295 | } 296 | 297 | inherit(Pokemon, _calc); 298 | inherit(Pokemon, _actions); -------------------------------------------------------------------------------- /src/models/World/Cell/index.js: -------------------------------------------------------------------------------- 1 | import s2 from "s2-geometry"; 2 | 3 | import Gym from "../Fort/Gym"; 4 | import Pokestop from "../Fort/Pokestop"; 5 | import MapObject from "../MapObject"; 6 | import SpawnPoint from "../SpawnPoint"; 7 | import WildPokemon from "../../Pokemon/WildPokemon"; 8 | 9 | import Settings from "../../../modes"; 10 | import CFG from "../../../../cfg"; 11 | 12 | import print from "../../../print"; 13 | 14 | const S2Geo = s2.S2; 15 | const MAP_REFRESH_RATE = Settings.GAME_SETTINGS.map_settings.get_map_objects_max_refresh_seconds; 16 | 17 | /** 18 | * @class Cell 19 | */ 20 | export default class Cell extends MapObject { 21 | 22 | /** 23 | * @param {Object} obj 24 | * @constructor 25 | */ 26 | constructor(obj) { 27 | 28 | super(obj); 29 | 30 | this.forts = []; 31 | 32 | this.type = obj.type; 33 | 34 | this.synced = false; 35 | 36 | this.expiration = 0; 37 | 38 | } 39 | 40 | /** 41 | * @param {Number} lat 42 | * @param {Number} lng 43 | * @return {String} 44 | */ 45 | static getIdByPosition(lat, lng, zoom) { 46 | return ( 47 | S2Geo.keyToId(S2Geo.latLngToKey(lat, lng, zoom || 15)) 48 | ); 49 | } 50 | 51 | delete() { 52 | let index = this.world.getCellIndexByCellId(this.cellId); 53 | let cell = this.world.cells[index]; 54 | if (cell) { 55 | this.world.cells.splice(index, 1); 56 | print(`Cell ${cell.cellId} timed out!`, 33); 57 | } 58 | } 59 | 60 | /** 61 | * @param {Object} obj 62 | * @return {Fort} 63 | */ 64 | addFort(obj) { 65 | obj.world = this.world; 66 | let fort = ( 67 | obj.type === "CHECKPOINT" ? new Pokestop(obj) : 68 | obj.type === "SPAWN" ? new SpawnPoint(obj) : 69 | new Gym(obj) 70 | ); 71 | this.forts.push(fort); 72 | return (fort); 73 | } 74 | 75 | refreshSpawnPoints() { 76 | this.forts.map((fort) => { 77 | if (fort.isSpawn === true) { 78 | fort.refresh(); 79 | } 80 | }); 81 | } 82 | 83 | loadForts() { 84 | return new Promise((resolve) => { 85 | this.expiration = +new Date() + CFG.CELL_TIMEOUT; 86 | if (this.synced) { 87 | this.forts.map((fort) => { 88 | this.processDeletedFort(fort); 89 | }); 90 | resolve(this.forts); 91 | } 92 | else { 93 | this.getFortsFromDatabase().then((forts) => { 94 | this.forts = []; 95 | forts.map((fort) => { 96 | this.processDeletedFort(this.addFort(fort)); 97 | }); 98 | this.synced = true; 99 | print(`Synced ${this.cellId} with database..`, 33); 100 | resolve(this.forts); 101 | }); 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * @param {String} type 108 | * @return {String} 109 | */ 110 | static getFortTable(type) { 111 | return ( 112 | type === "CHECKPOINT" ? 113 | CFG.MYSQL_POKESTOP_TABLE : 114 | type === "SPAWN" ? 115 | CFG.MYSQL_SPAWN_TABLE : 116 | type === "GYM" ? 117 | CFG.MYSQL_GYM_TABLE : 118 | "INVALID" 119 | ); 120 | } 121 | 122 | getSpawnsFromDatabase() { 123 | return new Promise((resolve) => { 124 | this.world.instance.getQueryByColumnFromTable("cell_id", this.cellId, CFG.MYSQL_SPAWN_TABLE).then((spawns) => { 125 | resolve(spawns || []); 126 | }); 127 | }); 128 | } 129 | 130 | getFortsFromDatabase() { 131 | return new Promise((resolve) => { 132 | let out = []; 133 | this.world.instance.getQueryByColumnFromTable("cell_id", this.cellId, CFG.MYSQL_POKESTOP_TABLE).then((forts) => { 134 | forts = forts || []; 135 | forts.map((fort) => { 136 | fort.type = "CHECKPOINT"; 137 | out.push(fort); 138 | }); 139 | this.world.instance.getQueryByColumnFromTable("cell_id", this.cellId, CFG.MYSQL_GYM_TABLE).then((forts) => { 140 | forts = forts || []; 141 | forts.map((fort) => { 142 | fort.type = "GYM"; 143 | out.push(fort); 144 | }); 145 | this.world.instance.getQueryByColumnFromTable("cell_id", this.cellId, CFG.MYSQL_SPAWN_TABLE).then((forts) => { 146 | forts = forts || []; 147 | forts.map((fort) => { 148 | fort.type = "SPAWN"; 149 | out.push(fort); 150 | }); 151 | resolve(out); 152 | }); 153 | }); 154 | }); 155 | }); 156 | } 157 | 158 | /** 159 | * Dirty hack to display disappearing forts 160 | * seems like game engine doesnt support dat 161 | * @param {Fort} fort 162 | */ 163 | processDeletedFort(fort) { 164 | if (fort.deleted) { 165 | fort.latitude = 0; 166 | fort.longitude = 0; 167 | fort.enabled = false; 168 | // wait for the next map refresh 169 | setTimeout(() => { 170 | this.deleteFortById(fort.uid); 171 | this.deleteFortFromDatabase(fort).then(() => { 172 | // do sth after 173 | }); 174 | }, (MAP_REFRESH_RATE * 1e3) * 3); 175 | } 176 | } 177 | 178 | /** 179 | * @param {Fort} fort 180 | */ 181 | deleteFortFromDatabase(fort) { 182 | return new Promise((resolve) => { 183 | let table = Cell.getFortTable(fort.type); 184 | this.world.instance.db.query(`DELETE FROM ${table} WHERE cell_id=? AND id=? LIMIT 1`, [fort.cellId, fort.uid], (e, res) => { 185 | resolve(); 186 | }); 187 | }); 188 | } 189 | 190 | /** 191 | * @param {Number} id 192 | * @return {Number} 193 | */ 194 | getFortIndexById(id) { 195 | let index = 0; 196 | for (let fort of this.forts) { 197 | if (fort.uid === id) return (index); 198 | ++index; 199 | }; 200 | return (-1); 201 | } 202 | 203 | /** 204 | * @param {Number} id 205 | * @return {Fort} 206 | */ 207 | getFortById(id) { 208 | let index = this.getFortIndexById(id); 209 | if (index < 0) return (null); 210 | return (this.forts[index]); 211 | } 212 | 213 | /** 214 | * @param {Number} id 215 | * @return {Fort} 216 | */ 217 | deleteFortById(id) { 218 | let index = this.getFortIndexById(id); 219 | if (index < 0) return (null); 220 | return (this.forts.splice(index, 1)); 221 | } 222 | 223 | /** 224 | * @return {Array} 225 | */ 226 | serializeSpawnPoints() { 227 | let ii = 0; 228 | let length = this.forts.length; 229 | let out = []; 230 | let fort = null; 231 | for (; ii < length; ++ii) { 232 | fort = this.forts[ii]; 233 | if (!(fort.isSpawn === true)) continue; 234 | out.push(fort.serialize()); 235 | }; 236 | return (out); 237 | } 238 | 239 | /** 240 | * @param {Player} player 241 | * @return {Array} 242 | */ 243 | serializePkmns(player) { 244 | let ii = 0; 245 | let length = this.forts.length; 246 | let out = { 247 | wild: [], 248 | nearby: [], 249 | catchable: [] 250 | }; 251 | let fort = null; 252 | for (; ii < length; ++ii) { 253 | fort = this.forts[ii]; 254 | if (!(fort.isSpawn === true)) continue; 255 | fort.activeSpawns.map((encounter) => { 256 | if ( 257 | !encounter.alreadyCatchedBy(player) && 258 | !encounter.isDespawned 259 | ) { 260 | out.wild.push(encounter.serializeWild()); 261 | out.nearby.push(encounter.serializeNearby()); 262 | out.catchable.push(encounter.serializeCatchable()); 263 | } 264 | }); 265 | }; 266 | return (out); 267 | } 268 | 269 | /** 270 | * @param {Player} player 271 | * @return {Object} 272 | */ 273 | serialize(player) { 274 | let buffer = { 275 | s2_cell_id: this.cellId, 276 | current_timestamp_ms: +new Date(), 277 | forts: this.forts.map((fort) => { if (!fort.isSpawn) return fort.serialize(); }), 278 | spawn_points: this.serializeSpawnPoints(), 279 | deleted_objects: [], 280 | fort_summaries: [], 281 | decimated_spawn_points: [], 282 | wild_pokemons: null, 283 | catchable_pokemons: null, 284 | nearby_pokemons: null 285 | }; 286 | let pkmns = this.serializePkmns(player); 287 | buffer.wild_pokemons = pkmns.wild; 288 | buffer.nearby_pokemons = pkmns.nearby; 289 | buffer.catchable_pokemons = pkmns.catchable; 290 | return (buffer); 291 | } 292 | 293 | } -------------------------------------------------------------------------------- /src/models/World/Fort/Gym/index.js: -------------------------------------------------------------------------------- 1 | import Fort from "../index"; 2 | 3 | import print from "../../../../print"; 4 | import CFG from "../../../../../cfg"; 5 | 6 | import { 7 | validName 8 | } from "../../../../utils"; 9 | 10 | import ENUM from "../../../../enum"; 11 | 12 | /** 13 | * @class Gym 14 | */ 15 | export default class Gym extends Fort { 16 | 17 | /** 18 | * @param {Object} obj 19 | * @constructor 20 | */ 21 | constructor(obj) { 22 | 23 | super(obj); 24 | 25 | this.team = 0; 26 | 27 | this.guardPkmn = 0; 28 | this.guardPkmnCp = 0; 29 | 30 | this.gymPoints = 0; 31 | 32 | this.isInBattle = 0; 33 | 34 | this.init(obj); 35 | 36 | } 37 | 38 | /** 39 | * @return {Object} 40 | */ 41 | serialize() { 42 | return ({ 43 | id: this.getId(), 44 | last_modified_timestamp_ms: +new Date(), 45 | latitude: this.latitude, 46 | longitude: this.longitude, 47 | enabled: this.enabled, 48 | owned_by_team: ENUM.getNameById(ENUM.TEAM, this.team), 49 | guard_pokemon_id: this.guardPkmn, 50 | gym_points: this.gymPoints, 51 | active_fort_modifier: [] 52 | }); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/models/World/Fort/Pokestop/index.js: -------------------------------------------------------------------------------- 1 | import Fort from "../index"; 2 | 3 | import print from "../../../../print"; 4 | import CFG from "../../../../../cfg"; 5 | 6 | import { 7 | validName 8 | } from "../../../../utils"; 9 | 10 | import ENUM from "../../../../enum"; 11 | 12 | /** 13 | * @class Gym 14 | */ 15 | export default class Pokestop extends Fort { 16 | 17 | /** 18 | * @param {Object} obj 19 | * @constructor 20 | */ 21 | constructor(obj) { 22 | 23 | super(obj); 24 | 25 | this.name = null; 26 | this.description = null; 27 | 28 | this.image_url = null; 29 | 30 | this.cooldown = 10e3; 31 | 32 | this.experience = 0; 33 | 34 | this.rewards = this.parseRewards(obj.rewards); 35 | 36 | this.init(obj); 37 | 38 | } 39 | 40 | /** 41 | * @return {Object} 42 | */ 43 | parseRewards(rewards) { 44 | rewards = rewards || "{}"; 45 | let json = null; 46 | try { 47 | json = JSON.parse(rewards); 48 | } catch (e) { 49 | json = {}; 50 | print(e, 31); 51 | } 52 | return (json); 53 | } 54 | 55 | /** 56 | * @return {Array} 57 | */ 58 | serializeRewards() { 59 | let out = []; 60 | for (let key in this.rewards) { 61 | let amount = this.rewards[key] << 0; 62 | for (let ii = 0; ii < amount; ++ii) { 63 | out.push({ 64 | item_id: ENUM.getNameById(ENUM.ITEMS, key << 0) 65 | }); 66 | }; 67 | }; 68 | return (out); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/models/World/Fort/index.js: -------------------------------------------------------------------------------- 1 | import MapObject from "../MapObject"; 2 | 3 | import print from "../../../print"; 4 | import CFG from "../../../../cfg"; 5 | 6 | import { 7 | validName 8 | } from "../../../utils"; 9 | 10 | /** 11 | * @class Fort 12 | */ 13 | export default class Fort extends MapObject { 14 | 15 | /** 16 | * @param {Object} obj 17 | * @constructor 18 | */ 19 | constructor(obj) { 20 | 21 | super(obj); 22 | 23 | this.enabled = true; 24 | this.deleted = false; 25 | 26 | this.type = null; 27 | 28 | this.init(obj); 29 | 30 | this.uid += this.type[0].toUpperCase(); 31 | 32 | } 33 | 34 | /** 35 | * @param {Object} obj 36 | */ 37 | init(obj) { 38 | obj = obj || {}; 39 | for (let key in obj) { 40 | if (this.hasOwnProperty(key) && key !== "rewards") { 41 | this[key] = obj[key]; 42 | } 43 | }; 44 | } 45 | 46 | /** 47 | * @return {String} 48 | */ 49 | getId() { 50 | return ( 51 | this.cellId + "." + this.uid 52 | ); 53 | } 54 | 55 | delete() { 56 | this.deleted = true; 57 | } 58 | 59 | /** 60 | * @return {Object} 61 | */ 62 | serialize() { 63 | return ({ 64 | id: this.getId(), 65 | last_modified_timestamp_ms: +new Date(), 66 | latitude: this.latitude, 67 | longitude: this.longitude, 68 | enabled: this.enabled, 69 | type: this.type 70 | }); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/models/World/MapObject/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class MapObject 3 | */ 4 | export default class MapObject { 5 | 6 | /** 7 | * @param {Object} obj 8 | * @constructor 9 | */ 10 | constructor(obj) { 11 | 12 | this.uid = 0; 13 | 14 | this.cellId = null; 15 | 16 | this.world = null; 17 | 18 | this.altitude = 0; 19 | this.latitude = 0; 20 | this.longitude = 0; 21 | 22 | this._init(obj); 23 | 24 | } 25 | 26 | /** 27 | * @param {Object} obj 28 | */ 29 | _init(obj) { 30 | obj = obj || {}; 31 | for (let key in obj) { 32 | if (this.hasOwnProperty(key)) { 33 | this[key] = obj[key]; 34 | } 35 | else if (key === "id") { 36 | this.uid = obj[key]; 37 | } 38 | else if (key === "cell_id") { 39 | this.cellId = obj[key]; 40 | } 41 | }; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/models/World/SpawnPoint/index.js: -------------------------------------------------------------------------------- 1 | import rare from "pokerare"; 2 | 3 | import MapObject from "../MapObject"; 4 | import WildPokemon from "../../Pokemon/WildPokemon"; 5 | 6 | import print from "../../../print"; 7 | import CFG from "../../../../cfg"; 8 | 9 | import Settings from "../../../modes"; 10 | 11 | const pokename = require("pokename")(); 12 | 13 | const MAP_REFRESH_RATE = Settings.GAME_SETTINGS.map_settings.get_map_objects_max_refresh_seconds; 14 | 15 | /** 16 | * @class SpawnPoint 17 | */ 18 | export default class SpawnPoint extends MapObject { 19 | 20 | /** 21 | * @param {Object} obj 22 | * @constructor 23 | */ 24 | constructor(obj) { 25 | 26 | super(obj); 27 | 28 | this.type = null; 29 | 30 | this.range = 3; 31 | 32 | this.spawns = JSON.parse(obj.encounters); 33 | 34 | this.minExpire = ((obj.min_spawn_expire) * 60) << 0; 35 | this.maxExpire = ((obj.max_spawn_expire) * 60) << 0; 36 | 37 | this.isSpawn = true; 38 | 39 | this.activeSpawns = []; 40 | 41 | this.init(obj); 42 | 43 | this.uid += this.type[0].toUpperCase(); 44 | 45 | } 46 | 47 | /** 48 | * @param {Object} obj 49 | */ 50 | init(obj) { 51 | obj = obj || {}; 52 | for (let key in obj) { 53 | if (this.hasOwnProperty(key)) { 54 | this[key] = obj[key]; 55 | } 56 | }; 57 | } 58 | 59 | refresh() { 60 | this.activeSpawns.map((pkmn) => { 61 | if (pkmn.isExpired() && !pkmn.isDespawned) { 62 | pkmn.isDespawned = true; 63 | pkmn.despawnIn = +new Date() + ((MAP_REFRESH_RATE * 1e3) + pkmn.expiration); 64 | } 65 | else if (pkmn.isDespawned) { 66 | if (+new Date() >= pkmn.despawnIn) { 67 | this.despawnPkmn(pkmn); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | /** 74 | * @return {Object} 75 | */ 76 | getRandomPosition() { 77 | let range = this.range / 1e4; 78 | let latitude = this.latitude + (Math.random() * (range * 2)) - range; 79 | let longitude = this.longitude + (Math.random() * (range * 2)) - range; 80 | return ({ 81 | lat: latitude, 82 | lng: longitude 83 | }); 84 | } 85 | 86 | spawnPkmn() { 87 | let randId = this.spawns[Math.floor(Math.random() * this.spawns.length)]; 88 | let randPos = this.getRandomPosition(); 89 | let pkmn = new WildPokemon({ 90 | dexNumber: randId, 91 | latitude: randPos.lat, 92 | longitude: randPos.lng, 93 | isWild: true, 94 | cellId: this.cellId, 95 | spawnPointId: this.uid, 96 | minExpire: this.minExpire, 97 | maxExpire: this.maxExpire 98 | }); 99 | this.activeSpawns.push(pkmn); 100 | print(`Spawned 1x ${pkmn.getPkmnName()}:${pkmn.uid} at ${this.cellId}`); 101 | } 102 | 103 | /** 104 | * @param {WildPokemon} pkmn 105 | */ 106 | despawnPkmn(pkmn) { 107 | let index = 0; 108 | this.activeSpawns.map((encounter) => { 109 | if (encounter.uid === pkmn.uid) { 110 | print(`Killed 1x ${pkmn.getPkmnName()}:${pkmn.uid} at ${this.cellId}`, 33); 111 | this.activeSpawns.splice(index, 1); 112 | } 113 | index++; 114 | }); 115 | } 116 | 117 | /** 118 | * @param {String} id 119 | * @return {WildPokemon} 120 | */ 121 | getPkmnSpawnById(id) { 122 | let ii = 0; 123 | let length = this.activeSpawns.length; 124 | let spawn = null; 125 | for (; ii < length; ++ii) { 126 | spawn = this.activeSpawns[ii]; 127 | if (spawn.uid === id) return (spawn); 128 | } 129 | return (null); 130 | } 131 | 132 | /** 133 | * @return {Object} 134 | */ 135 | serialize() { 136 | return ({ 137 | latitude: this.latitude, 138 | longitude: this.longitude 139 | }); 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /src/models/World/forts.js: -------------------------------------------------------------------------------- 1 | import Cell from "./Cell"; 2 | 3 | import CFG from "../../../cfg"; 4 | 5 | import print from "../../print"; 6 | 7 | /** 8 | * @param {String} id 9 | */ 10 | export function getFortDataById(id) { 11 | 12 | id = id.split(".") || []; 13 | 14 | let uid = id[1]; 15 | let cellId = id[0]; 16 | 17 | let cell = this.getCellById(cellId); 18 | 19 | return new Promise((resolve) => { 20 | if (!cellId || !uid || !cell) return resolve(); 21 | cell.loadForts().then(() => { 22 | let fort = cell.getFortById(uid); 23 | resolve(fort); 24 | }); 25 | }); 26 | 27 | } 28 | 29 | /** 30 | * @param {Array} cells 31 | * @param {Array} out 32 | * @param {Number} index 33 | */ 34 | export function getFortsByCells(cells, out, index) { 35 | return new Promise((resolve) => { 36 | this.getFortsByCellId(cells[index]).then((result) => { 37 | out.push(result); 38 | if (++index >= cells.length) resolve(out); 39 | else resolve(this.getFortsByCells(cells, out, index)); 40 | }); 41 | }); 42 | } 43 | 44 | /** 45 | * @param {String} cellId 46 | */ 47 | export function getFortsByCellId(cellId) { 48 | return new Promise((resolve) => { 49 | if (!this.cellAlreadyRegistered(cellId)) { 50 | this.registerCell(cellId).then((cell) => { 51 | resolve(cell); 52 | }); 53 | } 54 | else { 55 | let cell = this.getCellById(cellId); 56 | cell.loadForts().then(() => { 57 | resolve(cell); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * @param {String} cellId 65 | */ 66 | export function registerCell(cellId) { 67 | let cell = new Cell({ 68 | world: this, 69 | cellId: cellId 70 | }); 71 | this.cells.push(cell); 72 | return new Promise((resolve) => { 73 | cell.loadForts().then(() => { 74 | resolve(this.getCellById(cellId)); 75 | }); 76 | }); 77 | } 78 | 79 | /** 80 | * @param {Object} obj 81 | */ 82 | export function addFort(obj) { 83 | let cellId = Cell.getIdByPosition(obj.latitude, obj.longitude, obj.zoom); 84 | return new Promise((resolve) => { 85 | this.getFortsByCellId(cellId).then((cell) => { 86 | let fort = cell.addFort(obj); 87 | resolve(fort); 88 | }); 89 | }); 90 | } 91 | 92 | export function deleteFort(cellId, uid) { 93 | let cell = this.getCellById(cellId); 94 | let fort = cell.getFortById(uid); 95 | return new Promise((resolve) => { 96 | fort.delete(); 97 | resolve(); 98 | }); 99 | } 100 | 101 | /** 102 | * @param {Object} obj 103 | */ 104 | export function insertFortIntoDatabase(obj) { 105 | return new Promise((resolve) => { 106 | if (obj.type === "CHECKPOINT") { 107 | this.insertPokestopIntoDatabase(obj).then((gym) => { 108 | resolve(gym); 109 | }); 110 | } 111 | else if (obj.type === "GYM") { 112 | this.insertGymIntoDatabase(obj).then((pokestop) => { 113 | resolve(pokestop); 114 | }); 115 | } 116 | else if (obj.type === "SPAWN") { 117 | this.insertSpawnIntoDatabase(obj).then((spawn) => { 118 | resolve(spawn); 119 | }); 120 | } 121 | }); 122 | } 123 | 124 | export function insertPokestopIntoDatabase(obj) { 125 | let cellId = Cell.getIdByPosition(obj.latitude, obj.longitude, obj.zoom); 126 | let lat = obj.latitude; 127 | let lng = obj.longitude; 128 | let name = obj.name; 129 | let desc = obj.description; 130 | let img = obj.imageUrl || ""; 131 | let exp = obj.experience || 500; 132 | let query = `INSERT INTO ${Cell.getFortTable(obj.type)} SET cell_id=?, latitude=?, longitude=?, name=?, description=?, image_url=?, experience=?, rewards=?`; 133 | return new Promise((resolve) => { 134 | this.instance.db.query(query, [cellId, lat, lng, name, desc, img, exp, ""], (e, res) => { 135 | let insertId = res.insertId; 136 | obj.uid = insertId; 137 | obj.cell_id = cellId; 138 | this.addFort(obj).then((fort) => { 139 | resolve(fort); 140 | }); 141 | }); 142 | }); 143 | } 144 | 145 | export function insertGymIntoDatabase(obj) { 146 | let cellId = Cell.getIdByPosition(obj.latitude, obj.longitude, obj.zoom); 147 | let lat = obj.latitude; 148 | let lng = obj.longitude; 149 | let query = `INSERT INTO ${Cell.getFortTable(obj.type)} SET cell_id=?, latitude=?, longitude=?, team=?, in_battle=?, points=?`; 150 | return new Promise((resolve) => { 151 | this.instance.db.query(query, [cellId, lat, lng, 0, 0, 0], (e, res) => { 152 | let insertId = res.insertId; 153 | obj.uid = insertId; 154 | obj.cell_id = cellId; 155 | this.addFort(obj).then((fort) => { 156 | resolve(fort); 157 | }); 158 | }); 159 | }); 160 | } 161 | 162 | export function insertSpawnIntoDatabase(obj) { 163 | let cellId = Cell.getIdByPosition(obj.latitude, obj.longitude, obj.zoom); 164 | let lat = obj.latitude; 165 | let lng = obj.longitude; 166 | let encounters = []; 167 | obj.encounters.split(",").map((encounter) => { 168 | encounters.push(encounter << 0); 169 | }); 170 | let query = `INSERT INTO ${Cell.getFortTable(obj.type)} SET cell_id=?, latitude=?, longitude=?, encounters=?, update_interval=?`; 171 | return new Promise((resolve) => { 172 | this.instance.db.query(query, [cellId, lat, lng, `[${encounters}]`, obj.interval << 0], (e, res) => { 173 | obj.uid = res.insertId; 174 | obj.cell_id = cellId; 175 | this.addFort(obj).then((fort) => { 176 | resolve(fort); 177 | }); 178 | }); 179 | }); 180 | } 181 | -------------------------------------------------------------------------------- /src/models/World/index.js: -------------------------------------------------------------------------------- 1 | import Cell from "./Cell"; 2 | 3 | import CFG from "../../../cfg"; 4 | 5 | import print from "../../print"; 6 | 7 | import * as _forts from "./forts"; 8 | import * as _players from "./players"; 9 | import * as _packets from "./packets"; 10 | 11 | import { inherit } from "../../utils"; 12 | 13 | /** 14 | * @class World 15 | */ 16 | export default class World { 17 | 18 | /** @constructor */ 19 | constructor(instance) { 20 | 21 | this.instance = instance; 22 | 23 | this.db = this.instance.db; 24 | 25 | this.players = []; 26 | 27 | this.cells = []; 28 | 29 | } 30 | 31 | get connectedPlayers() { 32 | return (this.players.length); 33 | } 34 | 35 | /** 36 | * @param {String} cellId 37 | * @return {Number} 38 | */ 39 | getCellIndexByCellId(cellId) { 40 | let ii = 0; 41 | let length = this.cells.length; 42 | for (; ii < length; ++ii) { 43 | if (this.cells[ii].cellId === cellId) return (ii); 44 | }; 45 | return (-1); 46 | } 47 | 48 | /** 49 | * @param {String} cellId 50 | * @return {Cell} 51 | */ 52 | getCellByCellId(cellId) { 53 | let index = this.getCellIndexByCellId(cellId); 54 | return (this.cells[index] || null); 55 | } 56 | 57 | /** 58 | * @param {String} cellId 59 | * @return {Boolean} 60 | */ 61 | cellAlreadyRegistered(cellId) { 62 | let cell = this.getCellByCellId(cellId); 63 | return ( 64 | cell !== null 65 | ); 66 | } 67 | 68 | /** 69 | * @param {String} cellId 70 | * @return {Cell} 71 | */ 72 | getCellById(cellId) { 73 | return (this.getCellByCellId(cellId)); 74 | } 75 | 76 | refreshSpawns() { 77 | let ii = 0; 78 | let length = this.cells.length; 79 | for (; ii < length; ++ii) { 80 | this.cells[ii].refreshSpawnPoints(); 81 | }; 82 | } 83 | 84 | /** 85 | * @param {Number} lat 86 | * @param {Number} lng 87 | */ 88 | triggerSpawnAt(lat, lng) { 89 | let cell = this.getCellById(Cell.getIdByPosition(lat, lng, 15)); 90 | // Wait until cell got registered 91 | if (cell === null) return void 0; 92 | cell.forts.map((fort) => { 93 | if (fort.isSpawn) { 94 | if (fort.activeSpawns.length >= fort.spawns.length) return void 0; 95 | fort.spawnPkmn(); 96 | } 97 | }); 98 | } 99 | 100 | /** 101 | * @param {String} id 102 | * @return {WildPokemon} 103 | */ 104 | getEncounterById(id) { 105 | id = parseInt(id); 106 | let ii = 0; 107 | let jj = 0; 108 | let fortLength = 0; 109 | let cellLength = this.cells.length; 110 | let cell = null; 111 | let fort = null; 112 | let pkmn = null; 113 | for (; ii < cellLength; ++ii) { 114 | cell = this.cells[ii]; 115 | fortLength = cell.forts.length; 116 | for (; jj < fortLength; ++jj) { 117 | fort = cell.forts[jj]; 118 | if (fort.isSpawn === true) { 119 | if ((pkmn = fort.getPkmnSpawnById(id)) !== null) { 120 | return (pkmn); 121 | } 122 | } 123 | }; 124 | jj = 0; 125 | }; 126 | return (pkmn); 127 | } 128 | 129 | /** 130 | * @param {String} type 131 | * @param {Object} msg 132 | */ 133 | getPacket(type, msg) { 134 | return new Promise((resolve) => { 135 | switch (type) { 136 | case "ENCOUNTER": 137 | resolve(this.Encounter(msg)); 138 | break; 139 | case "CATCH_POKEMON": 140 | resolve(this.CatchPokemon(msg)); 141 | break; 142 | case "FORT_SEARCH": 143 | this.FortSearch(msg).then((result) => { 144 | resolve(result); 145 | }); 146 | break; 147 | case "FORT_DETAILS": 148 | this.FortDetails(msg).then((result) => { 149 | resolve(result); 150 | }); 151 | break; 152 | case "GET_MAP_OBJECTS": 153 | this.GetMapObjects(msg).then((result) => { 154 | resolve(result); 155 | }); 156 | break; 157 | case "CHECK_CHALLENGE": 158 | resolve(this.CheckChallenge(msg)); 159 | break; 160 | case "GET_DOWNLOAD_URLS": 161 | resolve(this.GetDownloadUrls(msg)); 162 | break; 163 | case "DOWNLOAD_SETTINGS": 164 | resolve(this.DownloadSettings(msg)); 165 | break; 166 | case "DOWNLOAD_REMOTE_CONFIG_VERSION": 167 | resolve(this.DownloadRemoteConfigVersion(msg)); 168 | break; 169 | case "DOWNLOAD_ITEM_TEMPLATES": 170 | resolve(this.DownloadItemTemplates(msg)); 171 | break; 172 | }; 173 | }); 174 | } 175 | 176 | } 177 | 178 | inherit(World, _forts); 179 | inherit(World, _players); 180 | inherit(World, _packets); -------------------------------------------------------------------------------- /src/models/World/packets/CatchPokemon.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | */ 6 | export default function CatchPokemon(msg) { 7 | 8 | let buffer = null; 9 | let schema = "POGOProtos.Networking.Responses.CatchPokemonResponse"; 10 | 11 | let player = msg.player; 12 | let bag = player.bag; 13 | let ball = bag.getLocalItemKey(msg.pokeball); 14 | 15 | let pkmn = msg.player.currentEncounter; 16 | 17 | player.bag[ball] -= 1; 18 | 19 | return new Promise((resolve) => { 20 | // Invalid pkmn 21 | if (!pkmn) { 22 | player.currentEncounter = null; 23 | pkmn.caughtBy(player); 24 | buffer = { 25 | status: "CATCH_ERROR" 26 | }; 27 | // Missed 28 | } else if (!msg.hit_pokemon || !bag[ball]) { 29 | buffer = { 30 | status: "CATCH_MISSED" 31 | }; 32 | } else { 33 | // Fleed 34 | if (Math.random() < .1) { 35 | pkmn.caughtBy(player); 36 | player.currentEncounter = null; 37 | buffer = { 38 | status: "CATCH_FLEE" 39 | }; 40 | // Escaped 41 | } else if (Math.random() < .2) { 42 | buffer = { 43 | status: "CATCH_ESCAPE" 44 | }; 45 | // Catched? 46 | } else { 47 | player.catchPkmn(pkmn, msg.pokeball).then((result) => { 48 | resolve(POGOProtos.serialize(result, schema)); 49 | }); 50 | return void 0; 51 | } 52 | } 53 | resolve(POGOProtos.serialize(buffer, schema)); 54 | }); 55 | 56 | } -------------------------------------------------------------------------------- /src/models/World/packets/CheckChallenge.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | */ 6 | export default function CheckChallenge(msg) { 7 | 8 | let buffer = { 9 | challenge_url: " " 10 | }; 11 | 12 | return ( 13 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.CheckChallengeResponse") 14 | ); 15 | 16 | } -------------------------------------------------------------------------------- /src/models/World/packets/DownloadItemTemplates.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import * as shared from "../../../shared"; 4 | 5 | /** 6 | * @param {Object} msg 7 | * @return {Buffer} 8 | */ 9 | export default function DownloadItemTemplates(msg) { 10 | return ( 11 | shared.GAME_MASTER.serialize() 12 | ); 13 | } -------------------------------------------------------------------------------- /src/models/World/packets/DownloadRemoteConfigVersion.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | */ 6 | export default function DownloadRemoteConfigVersion(msg) { 7 | 8 | let buffer = { 9 | "result": "SUCCESS", 10 | "item_templates_timestamp_ms": "1471650700946", 11 | "asset_digest_timestamp_ms": "1467338276561000", 12 | "$unknownFields": [] 13 | } 14 | 15 | return ( 16 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.DownloadRemoteConfigVersionResponse") 17 | ); 18 | 19 | } -------------------------------------------------------------------------------- /src/models/World/packets/DownloadSettings.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import Settings from "../../../modes"; 4 | 5 | /** 6 | * @param {Object} msg 7 | * @return {Buffer} 8 | */ 9 | export default function DownloadSettings(msg) { 10 | 11 | let buffer = { 12 | hash: "2788184af4004004d6ab0740f7632983332106f6", 13 | settings: Settings.GAME_SETTINGS 14 | }; 15 | 16 | return ( 17 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.DownloadSettingsResponse") 18 | ); 19 | 20 | } -------------------------------------------------------------------------------- /src/models/World/packets/Encounter.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | /** 4 | * @param {Object} msg 5 | */ 6 | export default function Encounter(msg) { 7 | 8 | let player = msg.player; 9 | 10 | // Try to use cached encounter 11 | let encounter = player.currentEncounter; 12 | 13 | // Dont use cached encounter 14 | if ( 15 | encounter === null || 16 | encounter.uid !== parseInt(msg.encounter_id) 17 | ) { 18 | encounter = this.getEncounterById(msg.encounter_id); 19 | } 20 | 21 | let buffer = { 22 | status: "ENCOUNTER_SUCCESS", 23 | capture_probability: { 24 | pokeball_type: ["ITEM_POKE_BALL", "ITEM_GREAT_BALL", "ITEM_ULTRA_BALL"], 25 | capture_probability: [1, 1, 1] 26 | } 27 | }; 28 | 29 | // Invalid pkmn 30 | if (!encounter) { 31 | player.currentEncounter = null; 32 | buffer.status = "ENCOUNTER_NOT_FOUND"; 33 | } 34 | // Already encountered 35 | else if (encounter.alreadyCatchedBy(player)) { 36 | buffer.status = "ENCOUNTER_ALREADY_HAPPENED"; 37 | } 38 | // Encounter success 39 | else { 40 | player.currentEncounter = encounter; 41 | encounter.seenBy(player); 42 | buffer.wild_pokemon = encounter.serializeWild(); 43 | buffer.wild_pokemon.pokemon_data.cp = encounter.getSeenCp(player); 44 | } 45 | 46 | return ( 47 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.EncounterResponse") 48 | ); 49 | 50 | } -------------------------------------------------------------------------------- /src/models/World/packets/FortDetails.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import CFG from "../../../../cfg"; 4 | import print from "../../../print"; 5 | 6 | /** 7 | * @param {Object} msg 8 | * @return {Buffer} 9 | */ 10 | export default function FortDetails(msg) { 11 | 12 | return new Promise((resolve) => { 13 | this.getFortDataById(msg.fort_id).then((fort) => { 14 | if (!fort) return void 0; 15 | let url = fort.image_url; 16 | let buffer = { 17 | fort_id: msg.fort_id, 18 | name: fort.name, 19 | description: fort.description, 20 | image_urls: [ 21 | url 22 | ], 23 | type: "CHECKPOINT", 24 | latitude: fort.latitude, 25 | longitude: fort.longitude, 26 | modifiers: [] 27 | }; 28 | resolve( 29 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.FortDetailsResponse") 30 | ); 31 | }); 32 | }); 33 | 34 | } -------------------------------------------------------------------------------- /src/models/World/packets/FortSearch.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import CFG from "../../../../cfg"; 4 | import print from "../../../print"; 5 | 6 | /** 7 | * @param {Object} msg 8 | * @return {Buffer} 9 | */ 10 | export default function FortSearch(msg) { 11 | 12 | let player = msg.player; 13 | 14 | return new Promise((resolve) => { 15 | this.getFortDataById(msg.fort_id).then((fort) => { 16 | if (!fort) return void 0; 17 | player.consumeFortRewards(fort); 18 | // TODO: disable rewarding when on cooldown 19 | let buffer = ({ 20 | result: "SUCCESS", 21 | items_awarded: fort.serializeRewards(), 22 | experience_awarded: fort.experience, 23 | cooldown_complete_timestamp_ms: +new Date() + fort.cooldown, 24 | chain_hack_sequence_number: 2 25 | }); 26 | player.info.upgradeExp(fort.experience); 27 | resolve( 28 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.FortSearchResponse") 29 | ); 30 | }); 31 | }); 32 | 33 | } -------------------------------------------------------------------------------- /src/models/World/packets/GetDownloadUrls.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import CFG from "../../../../cfg"; 4 | import print from "../../../print"; 5 | 6 | import { GAME_ASSETS } from "../../../shared"; 7 | 8 | /** 9 | * @param {Object} msg 10 | * @return {Buffer} 11 | */ 12 | export default function GetDownloadUrls(msg) { 13 | 14 | let asset = null; 15 | let assets = GAME_ASSETS[msg.player.platform].decode.digest; 16 | let assetId = msg.asset_id[0]; 17 | 18 | let ii = 0; 19 | let length = assets.length; 20 | 21 | for (; ii < length; ++ii) { 22 | if ((asset = assets[ii]).asset_id === assetId) { 23 | break; 24 | } 25 | }; 26 | 27 | let buffer = { 28 | download_urls: [{ 29 | asset_id: assetId, 30 | url: `http://${CFG.LOCAL_IP || this.instance.getLocalIPv4()}:${CFG.PORT}/model/${asset.bundle_name}`, 31 | size: asset.size, 32 | checksum: asset.checksum 33 | }] 34 | }; 35 | 36 | return ( 37 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetDownloadUrlsResponse") 38 | ); 39 | 40 | } -------------------------------------------------------------------------------- /src/models/World/packets/GetMapObjects.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import CFG from "../../../../cfg"; 4 | import print from "../../../print"; 5 | 6 | /** 7 | * @param {Object} msg 8 | * @return {Buffer} 9 | */ 10 | export default function GetMapObjects(msg) { 11 | 12 | let latitude = msg.latitude; 13 | let longitude = msg.longitude; 14 | 15 | let mapCells = []; 16 | 17 | let buffer = { 18 | status: "SUCCESS", 19 | map_cells: mapCells 20 | }; 21 | 22 | return new Promise((resolve) => { 23 | this.getFortsByCells(msg.cell_id, [], 0).then((cells) => { 24 | cells.map((cell) => { 25 | if (cell.forts.length) { 26 | let ids = []; 27 | cell.forts.map((fort) => { 28 | let id = fort.cellId + "." + fort.uid; 29 | if (ids.indexOf(id) > -1) print(`Duplicated fort!!!! => ${id}`, 31); 30 | else ids.push(id); 31 | }); 32 | } 33 | mapCells.push(cell.serialize(msg.player)); 34 | }); 35 | resolve( 36 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetMapObjectsResponse") 37 | ); 38 | }); 39 | }); 40 | 41 | } -------------------------------------------------------------------------------- /src/models/World/packets/index.js: -------------------------------------------------------------------------------- 1 | export Encounter from "./Encounter"; 2 | export FortSearch from "./FortSearch"; 3 | export FortDetails from "./FortDetails"; 4 | export CatchPokemon from "./CatchPokemon"; 5 | export GetMapObjects from "./GetMapObjects"; 6 | export CheckChallenge from "./CheckChallenge"; 7 | export GetDownloadUrls from "./GetDownloadUrls"; 8 | export DownloadSettings from "./DownloadSettings"; 9 | export DownloadItemTemplates from "./DownloadItemTemplates"; 10 | export DownloadRemoteConfigVersion from "./DownloadRemoteConfigVersion"; -------------------------------------------------------------------------------- /src/models/World/players.js: -------------------------------------------------------------------------------- 1 | import Player from "../Player"; 2 | 3 | import CFG from "../../../cfg"; 4 | 5 | import print from "../../print"; 6 | 7 | /** 8 | * @return {Boolean} 9 | */ 10 | export function isFull() { 11 | return ( 12 | this.connectedPlayers >= CFG.MAX_CONNECTIONS 13 | ); 14 | } 15 | 16 | /** 17 | * @param {Request} req 18 | * @param {Response} res 19 | */ 20 | export function getPlayerByRequest(req, res) { 21 | 22 | let player = null; 23 | 24 | if (this.playerAlreadyConnected(req)) { 25 | player = this.getPlayerByIP(req.headers.host); 26 | return (player); 27 | } 28 | else { 29 | player = this.addPlayer(req, res); 30 | return (player); 31 | } 32 | 33 | } 34 | 35 | /** 36 | * @param {Request} req 37 | * @return {Boolean} 38 | */ 39 | export function playerAlreadyConnected(req) { 40 | 41 | let ii = 0; 42 | let length = this.connectedPlayers; 43 | 44 | let remoteAddress = req.headers.host; 45 | 46 | for (; ii < length; ++ii) { 47 | if (this.players[ii].remoteAddress === remoteAddress) { 48 | return (true); 49 | } 50 | }; 51 | 52 | return (false); 53 | 54 | } 55 | 56 | /** 57 | * @param {String} ip 58 | */ 59 | export function getPlayerByIP(ip) { 60 | 61 | let players = this.players; 62 | 63 | let ii = 0; 64 | let length = this.connectedPlayers; 65 | 66 | for (; ii < length; ++ii) { 67 | if (players[ii].remoteAddress === ip) { 68 | return (players[ii]); 69 | } 70 | }; 71 | 72 | return (null); 73 | 74 | } 75 | 76 | /** 77 | * @param {String} name 78 | */ 79 | export function getPlayerByName(name) { 80 | 81 | let players = this.players; 82 | 83 | let ii = 0; 84 | let length = this.connectedPlayers; 85 | 86 | for (; ii < length; ++ii) { 87 | if (players[ii].username === name) { 88 | return (players[ii]); 89 | } 90 | }; 91 | 92 | return (null); 93 | 94 | } 95 | 96 | /** 97 | * @param {String} name 98 | * @return {Boolean} 99 | */ 100 | export function validPlayerName(name) { 101 | return !( 102 | name === null || 103 | name === void 0 || 104 | typeof name === "string" && 105 | name.length <= 3 || 106 | name.length > 16 107 | ); 108 | } 109 | 110 | /** 111 | * @param {String} email 112 | */ 113 | export function playerIsRegistered(email) { 114 | return new Promise((resolve) => { 115 | this.db.query(`SELECT * FROM ${CFG.MYSQL_USERS_TABLE} WHERE email=?`, [email], (e, rows) => { 116 | if (e) return print(e, 31); 117 | resolve(rows.length >= 1); 118 | }); 119 | }); 120 | } 121 | 122 | /** 123 | * @param {Player} player 124 | */ 125 | export function registerPlayer(player) { 126 | return new Promise((resolve) => { 127 | this.db.query(`INSERT INTO ${CFG.MYSQL_USERS_TABLE} SET email=?, username=? `, [player.email, player.username], (e, res) => { 128 | if (e) return print(e, 31); 129 | resolve(); 130 | }); 131 | }); 132 | } 133 | 134 | /** 135 | * @param {Request} req 136 | * @param {Response} res 137 | * @return {Player} 138 | */ 139 | export function addPlayer(req, res) { 140 | 141 | let player = new Player({ 142 | world: this, 143 | request: req, 144 | response: res 145 | }); 146 | 147 | player.remoteAddress = req.headers.host; 148 | 149 | this.players.push(player); 150 | 151 | return (player); 152 | 153 | } 154 | 155 | /** 156 | * @param {Player} player 157 | */ 158 | export function removePlayer(player) { 159 | print(`Removed ${player.email}`, 36); 160 | } 161 | 162 | /** 163 | * @param {String} name 164 | */ 165 | export function kickPlayer(name) { 166 | 167 | if (!this.validPlayerName(name)) { 168 | print(`Invalid player name!`, 31); 169 | return void 0; 170 | } 171 | 172 | let player = this.getPlayerByName(name); 173 | 174 | if (player !== null) { 175 | this.removePlayer(player); 176 | print(`Kicked ${name} from the server!`); 177 | } 178 | else print(`Failed to kick ${name} from the server!`, 31); 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/modes/index.js: -------------------------------------------------------------------------------- 1 | import CFG from "../../cfg"; 2 | 3 | export default { 4 | GAME_SETTINGS: { 5 | fort_settings: { 6 | interaction_range_meters: 40.25098039215686, 7 | max_total_deployed_pokemon: 10, 8 | max_player_deployed_pokemon: 1, 9 | deploy_stamina_multiplier: 8.062745098039215, 10 | deploy_attack_multiplier: 0, 11 | far_interaction_range_meters: 1000.0156862745098 12 | }, 13 | map_settings: { 14 | pokemon_visible_range: 999.00196078431372, 15 | poke_nav_range_meters: 751.0156862745098, 16 | encounter_range_meters: 999.25098039215686, 17 | get_map_objects_min_refresh_seconds: 16, 18 | get_map_objects_max_refresh_seconds: 16, 19 | get_map_objects_min_distance_meters: 10.007843017578125, 20 | google_maps_api_key: CFG.GMAPS_KEY 21 | }, 22 | inventory_settings: { 23 | max_pokemon: 1000, 24 | max_bag_items: 1000, 25 | base_pokemon: 250, 26 | base_bag_items: 350, 27 | base_eggs: 9 28 | }, 29 | minimum_client_version: CFG.MINIMUM_CLIENT_VERSION 30 | }, 31 | PKMN_SETTINGS: { 32 | MIN_IV: 1, 33 | MAX_IV: 15 34 | } 35 | } -------------------------------------------------------------------------------- /src/print.js: -------------------------------------------------------------------------------- 1 | import CFG from "../cfg"; 2 | 3 | /** 4 | * @param {String} msg 5 | * @param {Number} color 6 | * @param {Boolean} nl 7 | */ 8 | export default function print(msg, color, newline) { 9 | color = Number.isInteger(color) ? color : CFG.DEFAULT_CONSOLE_COLOR; 10 | process.stdout.write(`[Console] \x1b[${color};1m${msg}\x1b[0m${newline === void 0 ? "\n" : ""}`); 11 | } -------------------------------------------------------------------------------- /src/process.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | import print from "./print"; 4 | import CFG from "../cfg"; 5 | 6 | const helpMessage = fs.readFileSync(".help", "utf8"); 7 | 8 | export function processCommand(cmd, data) { 9 | let players = this.world.players; 10 | switch (cmd) { 11 | // How many active connections there are 12 | case "/players": 13 | var length = players.length; 14 | print(`${length}/${CFG.MAX_CONNECTIONS} connected players!`, 33); 15 | break; 16 | // Exit the server 17 | case "/exit": 18 | this.shutdown(); 19 | break; 20 | case "/kick": 21 | this.world.kickPlayer(data[0]); 22 | break; 23 | case "/kickall": 24 | var length = players.length; 25 | this.removeAllPlayers(); 26 | var result = length - players.length; 27 | print(`Removed ${result} player${result === 1 ? "": "s"}!`); 28 | break; 29 | case "/clear": 30 | process.stdout.write("\x1Bc"); 31 | this.greet(); 32 | break; 33 | case "/help": 34 | console.log("\x1b[36;1m==================================== HELP =====================================\x1b[0m"); 35 | console.log(`\x1b[${CFG.DEFAULT_CONSOLE_COLOR};1m${helpMessage}\x1b[0m`); 36 | console.log("\x1b[36;1m===============================================================================\x1b[0m"); 37 | break; 38 | case "/save": 39 | this.saveAllPlayers(); 40 | var length = players.length; 41 | print(`Saved ${length} player${length === 1 ? "": "s"} into database!`); 42 | break; 43 | case "/spawn": 44 | this.spawnPkmnAtPlayer(data[0], data[1], data[2] || 1); 45 | break; 46 | case "/dump": 47 | print("Preparing dump session.."); 48 | this.onFirstRun(() => { 49 | print("Dumped assets successfully!"); 50 | }); 51 | break; 52 | default: 53 | print(`${cmd} is not a valid command!`, 31); 54 | break; 55 | }; 56 | }; 57 | 58 | export function stdinInput(data) { 59 | if (data.length <= 1) return void 0; 60 | let args = data.split(" "); 61 | let cmds = args.shift() 62 | this.processCommand(cmds, args); 63 | }; 64 | 65 | export function uncaughtException(e) { 66 | switch (e.errno) { 67 | case "EADDRINUSE": 68 | print(`Port ${CFG.PORT} is already in use!`, 31); 69 | break; 70 | case "EACCES": 71 | print("No root privileges!", 31); 72 | break; 73 | default: 74 | print("Unhandled exception occurred: ", 31); 75 | print(e, 31); 76 | print(e.stack, 31); 77 | break; 78 | }; 79 | print("The server has crashed!", 31); 80 | }; -------------------------------------------------------------------------------- /src/request.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import url from "url"; 3 | import jwtDecode from "jwt-decode"; 4 | import POGOProtos from "pokemongo-protobuf"; 5 | 6 | import print from "./print"; 7 | import CFG from "../cfg"; 8 | 9 | import { 10 | deXOR, 11 | validEmail, 12 | getHashCodeFrom 13 | } from "./utils"; 14 | 15 | /** 16 | * @param {Request} req 17 | * @param {Response} res 18 | */ 19 | export function routeRequest(req, res) { 20 | 21 | let parsed = url.parse(req.url).pathname; 22 | let route = parsed.split("/"); 23 | let host = req.headers.host; 24 | 25 | switch (route[1]) { 26 | case "plfe": 27 | case "custom": 28 | this.processRpcRequest(req, res, route); 29 | break; 30 | case "model": 31 | this.processModelRequest(req, res, route); 32 | break; 33 | case "api": 34 | if (!CFG.API_ENABLE) { 35 | print(`API is disabled! Denied API access for ${host}!`, 31); 36 | return void 0; 37 | } 38 | if (req.method === "POST") { 39 | this.processApiCall(req, res, route); 40 | } 41 | break; 42 | default: 43 | print(`Unknown request url: https://${host}${req.url}`, 31); 44 | break; 45 | }; 46 | 47 | } 48 | 49 | /** 50 | * @param {Request} req 51 | * @param {Response} res 52 | * @param {Array} route 53 | */ 54 | export function processModelRequest(req, res, route) { 55 | 56 | let player = this.world.getPlayerByRequest(req, res); 57 | 58 | // make sure no random dudes can access download 59 | if (!player.authenticated) return void 0; 60 | let name = route[2]; 61 | if (name && name.length > 1) { 62 | let folder = player.isAndroid ? "android/" : "ios/"; 63 | fs.readFile("data/" + folder + name, (error, data) => { 64 | if (error) { 65 | print(`Error file resolving model ${name}:` + error, 31); 66 | return void 0; 67 | } 68 | print(`Sent ${name} to ${player.email}`, 36); 69 | res.end(data); 70 | }); 71 | } 72 | 73 | } 74 | 75 | /** 76 | * @param {Request} req 77 | * @param {Response} res 78 | * @param {Array} route 79 | */ 80 | export function processRpcRequest(req, res, route) { 81 | let player = this.world.getPlayerByRequest(req, res); 82 | if (route[2] === "rpc") { 83 | player.refreshSocket(req, res); 84 | this.onRequest(player); 85 | } 86 | } 87 | 88 | /** 89 | * @param {Player} player 90 | */ 91 | export function onRequest(player) { 92 | 93 | let request = player.request; 94 | request.requests = request.requests || []; 95 | 96 | if (!player.authenticated) { 97 | this.authenticatePlayer(player); 98 | return void 0; 99 | } 100 | 101 | if (player.hasSignature === false) { 102 | player.getDevicePlatform(); 103 | } 104 | 105 | if (!request.requests.length) { 106 | // Dirty hack, appears when open pkmn stats in inventory 107 | if (request.unknown6 && request.unknown6[1].request_type === 6) { 108 | let msg = this.envelopResponse([], request, player); 109 | player.sendResponse(msg); 110 | } 111 | else { 112 | print("Received invalid request!", 31); 113 | return void 0; 114 | } 115 | } 116 | 117 | this.processRequests(player, request.requests).then((returns) => { 118 | if (CFG.DEBUG_DUMP_TRAFFIC) { 119 | this.dumpTraffic(request, returns); 120 | } 121 | let msg = this.envelopResponse(returns, request, player); 122 | if (CFG.DEBUG_LOG_REQUESTS) { 123 | print(`##### ${this.getCurrentTime()}`); 124 | let index = 0; 125 | request.requests.map((request) => { 126 | let reqSize = Buffer.byteLength(request.request_message, "utf8"); 127 | let resSize = Buffer.byteLength(returns[index], "utf8"); 128 | print(`${request.request_type} ${reqSize} => ${resSize}`, 37); 129 | index++; 130 | }); 131 | } 132 | player.sendResponse(msg); 133 | }); 134 | 135 | } 136 | 137 | /** 138 | * @param {Array} returns 139 | * @param {Request} request 140 | * @param {Player} player 141 | * @return {Buffer} 142 | */ 143 | export function envelopResponse(returns, request, player) { 144 | 145 | let buffer = request; 146 | 147 | buffer.returns = returns; 148 | 149 | if (request.auth_ticket) { 150 | print("Authenticate!", 31); 151 | buffer.auth_ticket = AuthTicket(); 152 | } 153 | 154 | if (buffer.unknown6) { 155 | buffer.unknown6 = [{ 156 | "response_type": 6, 157 | "unknown2": { 158 | "unknown1": "1", 159 | "items": [], 160 | "player_currencies": [] 161 | } 162 | }]; 163 | } 164 | 165 | buffer.status_code = 1; 166 | 167 | return ( 168 | POGOProtos.serialize(buffer, "POGOProtos.Networking.Envelopes.ResponseEnvelope") 169 | ); 170 | 171 | } 172 | 173 | /** 174 | * @param {Player} player 175 | */ 176 | export function authenticatePlayer(player) { 177 | 178 | let request = player.request; 179 | let token = request.auth_info; 180 | let msg = player.GetAuthTicket(request.request_id); 181 | 182 | if (!token || !token.provider) { 183 | print("Invalid authentication token! Kicking..", 31); 184 | player.world.removePlayer(player); 185 | return void 0; 186 | } 187 | 188 | if (token.provider === "google") { 189 | if (token.token !== null) { 190 | let decoded = jwtDecode(token.token.contents); 191 | player.email = decoded.email; 192 | player.email_verified = decoded.email_verified; 193 | player.isGoogleAccount = true; 194 | print(`${player.email} connected!`, 36); 195 | } 196 | else { 197 | print("Invalid authentication token! Kicking..", 31); 198 | player.world.removePlayer(player); 199 | return void 0; 200 | } 201 | } 202 | else { 203 | print("Invalid provider! Kicking..", 31); 204 | player.world.removePlayer(player); 205 | return void 0; 206 | } 207 | 208 | if (!validEmail(player.email)) return void 0; 209 | 210 | player.authenticated = true; 211 | 212 | this.world.playerIsRegistered(player.email).then((truth) => { 213 | // Register 214 | if (!truth) { 215 | this.world.registerPlayer(player).then((id) => { 216 | player.syncWithDatabase().then(() => { 217 | player.sendResponse(msg); 218 | }); 219 | }); 220 | } 221 | // Login 222 | else { 223 | player.syncWithDatabase().then(() => { 224 | player.sendResponse(msg); 225 | }); 226 | } 227 | }); 228 | 229 | } 230 | 231 | /** 232 | * @param {Player} player 233 | * @param {Array} requests 234 | * @return {Array} 235 | */ 236 | export function processRequests(player, requests) { 237 | 238 | return new Promise((resolve) => { 239 | 240 | let index = 0; 241 | let length = requests.length; 242 | let body = []; 243 | 244 | const loop = (index) => { 245 | this.processResponse(player, requests[index]).then((request) => { 246 | body.push(request); 247 | if (++index >= length) resolve(body); 248 | else return loop(index); 249 | }); 250 | }; 251 | 252 | loop(0); 253 | 254 | }); 255 | 256 | } -------------------------------------------------------------------------------- /src/response.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | 3 | import print from "./print"; 4 | import CFG from "../cfg"; 5 | 6 | import { 7 | _toCC 8 | } from "./utils"; 9 | 10 | /** 11 | * @param {Request} req 12 | * @param {String} type 13 | * @return {Buffer} 14 | */ 15 | export function parseMessage(req, type) { 16 | let proto = `POGOProtos.Networking.Requests.Messages.${type}Message`; 17 | if (req.request_message) { 18 | try { 19 | return (this.parseProtobuf(req.request_message, proto)); 20 | } catch (e) { 21 | print(`Failed to parse ${type}: ${e}`, 31); 22 | } 23 | } 24 | return void 0; 25 | } 26 | 27 | /** 28 | * @param {Player} player 29 | * @param {Request} req 30 | * @return {Buffer} 31 | */ 32 | export function processResponse(player, req) { 33 | 34 | let cc = _toCC(req.request_type); 35 | let msg = this.parseMessage(req, cc) || {}; 36 | 37 | return new Promise((resolve) => { 38 | 39 | try { 40 | switch (req.request_type) { 41 | // Player 42 | case "SET_AVATAR": 43 | case "GET_PLAYER": 44 | case "GET_INVENTORY": 45 | case "RELEASE_POKEMON": 46 | case "UPGRADE_POKEMON": 47 | case "GET_ASSET_DIGEST": 48 | case "NICKNAME_POKEMON": 49 | case "CLAIM_CODENAME": 50 | case "GET_HATCHED_EGGS": 51 | case "LEVEL_UP_REWARDS": 52 | case "GET_PLAYER_PROFILE": 53 | case "CHECK_AWARDED_BADGES": 54 | case "SET_FAVORITE_POKEMON": 55 | case "RECYCLE_INVENTORY_ITEM": 56 | player.getPacket(req.request_type, msg).then((result) => { 57 | resolve(result); 58 | }); 59 | return void 0; 60 | break; 61 | // Global 62 | case "ENCOUNTER": 63 | case "FORT_SEARCH": 64 | case "FORT_DETAILS": 65 | case "CATCH_POKEMON": 66 | case "GET_MAP_OBJECTS": 67 | case "CHECK_CHALLENGE": 68 | case "GET_DOWNLOAD_URLS": 69 | case "DOWNLOAD_SETTINGS": 70 | case "DOWNLOAD_REMOTE_CONFIG_VERSION": 71 | case "DOWNLOAD_ITEM_TEMPLATES": 72 | case "MARK_TUTORIAL_COMPLETE": 73 | msg.player = player; 74 | player.world.getPacket(req.request_type, msg).then((result) => { 75 | resolve(result); 76 | }); 77 | return void 0; 78 | break; 79 | default: 80 | print(`Unknown request: ${req.request_type}`, 31); 81 | break; 82 | }; 83 | } catch (e) { 84 | print(`Response error: ${e}`, 31); 85 | }; 86 | 87 | }); 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/setup.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import fse from "fs-extra"; 3 | import pogo from "pogo-asset-downloader"; 4 | import POGOProtos from "pokemongo-protobuf"; 5 | 6 | import print from "./print"; 7 | import CFG from "../cfg"; 8 | 9 | import * as shared from "./shared"; 10 | 11 | import GameMaster from "./models/GameMaster"; 12 | 13 | import { 14 | _toCC, 15 | capitalize, 16 | idToPkmnBundleName 17 | } from "./utils"; 18 | 19 | export function setup() { 20 | 21 | return new Promise((resolve, reject) => { 22 | 23 | // make sure all assets got loaded properly 24 | this.validateAssets().then(() => { 25 | 26 | print(`Downloaded assets are valid! Proceeding..`); 27 | 28 | shared.GAME_MASTER = new GameMaster(this); 29 | 30 | this.setupDatabaseConnection().then(() => { 31 | if (CFG.PORT < 1) { 32 | print("Invalid port!", 31); 33 | return void 0; 34 | } 35 | this.socket = this.createHTTPServer(); 36 | setTimeout(this::this.cycle, 1); 37 | let localIPv4 = this.getLocalIPv4(); 38 | print(`Server listening at ${localIPv4}:${CFG.PORT}`, 33); 39 | resolve(); 40 | }); 41 | 42 | }).catch((e) => { 43 | print("Error: " + e + " was not found!", 31); 44 | this.initDumpSession().then(resolve); 45 | }); 46 | 47 | }); 48 | 49 | } 50 | 51 | export function initDumpSession() { 52 | print("Required assets are missing! Preparing dump session..", 33); 53 | return new Promise((resolve, reject) => { 54 | setTimeout(() => { 55 | this.onFirstRun(() => { 56 | this.setup().then(resolve); 57 | }); 58 | }, 1e3); 59 | }); 60 | } 61 | 62 | /** 63 | * Make sure all required 64 | * assets got loaded properly 65 | */ 66 | export function validateAssets() { 67 | 68 | return new Promise((resolve, reject) => { 69 | 70 | // validate game master 71 | if (!this.fileExists(CFG.DUMP_ASSET_PATH + "game_master")) { 72 | return reject("File game_master"); 73 | } 74 | 75 | this.validateModels().then(() => { 76 | resolve(); 77 | }).catch((e) => { 78 | reject(e); 79 | }); 80 | 81 | }); 82 | 83 | } 84 | 85 | export function validateModels() { 86 | 87 | let max = CFG.MAX_POKEMON_NATIONAL_ID; 88 | let limit = pogo.platforms.length; 89 | 90 | return new Promise((resolve, reject) => { 91 | const validate = (index) => { 92 | let platform = pogo.platforms[index].name; 93 | let path = CFG.DUMP_ASSET_PATH + platform + "/"; 94 | 95 | // ups, validate asset_digest's too 96 | if (!this.fileExists(path + "asset_digest")) { 97 | return reject(`${path}asset_digest`); 98 | } 99 | else { 100 | let buffer = fs.readFileSync(path + "asset_digest"); 101 | shared.GAME_ASSETS[platform] = { 102 | buffer: buffer, 103 | decode: this.parseProtobuf(buffer, "POGOProtos.Networking.Responses.GetAssetDigestResponse") 104 | }; 105 | } 106 | 107 | // validate models inside folder 108 | let ii = 0; 109 | while (++ii <= max) { 110 | let id = idToPkmnBundleName(ii); 111 | if (!this.fileExists(path + id)) { 112 | return reject("Model " + id); 113 | } 114 | }; 115 | 116 | if (++index >= limit) { 117 | resolve(); 118 | return void 0; 119 | } 120 | validate(index); 121 | }; 122 | 123 | validate(0); 124 | }); 125 | 126 | } 127 | 128 | export function onFirstRun(resolve) { 129 | print(`Attempt to login with ${_toCC(CFG.DOWNLOAD_PROVIDER)}..`, 33); 130 | pogo.login({ 131 | provider: CFG.DOWNLOAD_PROVIDER, // google or ptc 132 | username: CFG.DOWNLOAD_USERNAME, 133 | password: CFG.DOWNLOAD_PASSWORD 134 | }).then(() => { 135 | print(`Successfully logged in!`); 136 | this.downloadAssetDigests().then(() => { 137 | this.downloadAssets().then(resolve); 138 | }); 139 | }).catch((e) => { 140 | print(e, 31); 141 | }); 142 | } 143 | 144 | export function downloadAssetDigests(assets) { 145 | return new Promise((resolve, reject) => { 146 | // create data folder for each support platform 147 | // and download each asset digest and related models 148 | let index = 0; 149 | let length = pogo.platforms.length; 150 | for (let platform of pogo.platforms) { 151 | fse.ensureDirSync(CFG.DUMP_ASSET_PATH + platform.name); 152 | pogo.getAssetDigest(platform).then((asset) => { 153 | fs.writeFileSync(CFG.DUMP_ASSET_PATH + platform.name + "/asset_digest", asset.toBuffer()); 154 | if (++index >= length) resolve(); 155 | }); 156 | }; 157 | }); 158 | } 159 | 160 | export function downloadAssets() { 161 | return new Promise((resolve, reject) => { 162 | pogo.getGameMaster().then((master) => { 163 | fs.writeFileSync(CFG.DUMP_ASSET_PATH + "game_master", master.toBuffer()); 164 | this.downloadModels().then(() => { 165 | resolve(); 166 | }); 167 | }); 168 | }); 169 | } 170 | 171 | export function downloadModels() { 172 | 173 | let limit = pogo.platforms.length; 174 | 175 | return new Promise((resolve, reject) => { 176 | const dump = (index) => { 177 | let platform = pogo.platforms[index]; 178 | let name = platform.name; 179 | let caps = capitalize(name); 180 | caps = name === "ios" ? "iOS" : caps; 181 | pogo.setPlatform(name); 182 | print(`Preparing to dump ${caps} assets..`, 36); 183 | this.dumpPkmnModels(CFG.DUMP_ASSET_PATH + name + "/", () => { 184 | print(`Dumped ${CFG.MAX_POKEMON_NATIONAL_ID} ${caps} assets successfully!`); 185 | if (++index >= limit) { 186 | print("Dumped all assets successfully!"); 187 | resolve(); 188 | return void 0; 189 | } 190 | dump(index); 191 | }); 192 | }; 193 | dump(0); 194 | }); 195 | 196 | } 197 | 198 | export function dumpPkmnModels(path, resolve) { 199 | 200 | let limit = CFG.MAX_POKEMON_NATIONAL_ID; 201 | 202 | const dump = (index) => { 203 | let ids = []; 204 | if (++index <= limit) ids.push(index); 205 | if (++index <= limit) ids.push(index); 206 | if (++index <= limit) ids.push(index); 207 | pogo.getAssetByPokemonId(ids).then((downloads) => { 208 | downloads.map((item) => { 209 | print(`Dumping model ${item.name}..`, 35); 210 | try { 211 | fs.writeFileSync(path + item.name, item.body); 212 | } 213 | catch (e) { 214 | print(`Error while dumping model ${item.name}:` + e, 31); 215 | } 216 | }); 217 | if (index >= limit) { 218 | //print(`Dumped ${limit} assets successfully!`); 219 | resolve(); 220 | return void 0; 221 | } 222 | setTimeout(() => dump(index), 1e3); 223 | }); 224 | }; 225 | 226 | dump(0); 227 | 228 | } -------------------------------------------------------------------------------- /src/shared.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global shared assets 3 | */ 4 | 5 | export let GAME_ASSETS = {}; 6 | export let GAME_MASTER = null; -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import POGOProtos from "pokemongo-protobuf"; 2 | import pcrypt from "pcrypt"; 3 | 4 | import CFG from "../cfg"; 5 | 6 | /** 7 | * @param {Object} cls 8 | * @param {Object} prot 9 | * @export 10 | */ 11 | export function inherit(cls, prot) { 12 | 13 | let key = null; 14 | 15 | for (key in prot) { 16 | if (prot[key] instanceof Function) { 17 | cls.prototype[key] = prot[key]; 18 | } 19 | }; 20 | 21 | } 22 | 23 | let hashIndex = 1; 24 | 25 | /** 26 | * @return {Number} 27 | */ 28 | export function getUniqueHash() { 29 | if (++hashIndex >= Number.MAX_SAFE_INTEGER) { 30 | hashIndex = 0; 31 | } 32 | return (hashIndex); 33 | } 34 | 35 | /** 36 | * http://stackoverflow.com/a/7616484/3367904 37 | * @param {String} str 38 | * @return {String} 39 | */ 40 | export function getHashCodeFrom(str) { 41 | var hash = 0, i, chr, len; 42 | if (str.length === 0) return hash; 43 | for (i = 0, len = str.length; i < len; i++) { 44 | chr = str.charCodeAt(i); 45 | hash = ((hash << 5) - hash) + chr; 46 | hash |= 0; // Convert to 32bit integer 47 | } 48 | return hash; 49 | } 50 | 51 | export function deXOR(value, hash) { 52 | let out = ""; 53 | let ii = 0; 54 | let length = value.length; 55 | for (; ii < length; ++ii) { 56 | out += String.fromCharCode(hash ^ value.charCodeAt(ii)); 57 | }; 58 | return (out); 59 | } 60 | 61 | /** 62 | * @return {Number} 63 | */ 64 | export function randomRequestId() { 65 | return ( 66 | 1e18 - Math.floor(Math.random() * 1e18) 67 | ); 68 | } 69 | 70 | /** 71 | * @param {String} key 72 | * @return {String} 73 | */ 74 | export function _toCC(key) { 75 | key = key.toLowerCase(); 76 | let res = key[0].toUpperCase() + key.substring(1, key.length).replace(/_\s*([a-z])/g, function(d, e) { 77 | return e.toUpperCase(); 78 | }); 79 | return (res); 80 | } 81 | 82 | /** 83 | * @param {Number} index 84 | * @return {String} 85 | */ 86 | export function idToPkmnBundleName(index) { 87 | return ( 88 | "pm" + (index >= 10 ? index >= 100 ? "0" : "00" : "000") + index 89 | ); 90 | } 91 | 92 | /** 93 | * @param {String} str 94 | * @return {String} 95 | */ 96 | export function capitalize(str) { 97 | return ( 98 | str[0].toUpperCase() + str.slice(1) 99 | ); 100 | } 101 | 102 | /** 103 | * @param {String} str 104 | * @return {String} 105 | */ 106 | export function deCapitalize(str) { 107 | return ( 108 | str[0].toLowerCase() + str.slice(1) 109 | ); 110 | } 111 | 112 | let rx_username = /[^a-z\d]/i; 113 | export function validUsername(str) { 114 | return ( 115 | !(rx_username.test(str)) 116 | ); 117 | } 118 | 119 | let rx_email = /\S+@\S+\.\S+/; 120 | export function validEmail(str) { 121 | return ( 122 | !!rx_email.test(str) 123 | ); 124 | } 125 | 126 | /** 127 | * @param {Request} req 128 | */ 129 | export function parseSignature(req) { 130 | let key = pcrypt.decrypt(req.unknown6[0].unknown2.encrypted_signature); 131 | return ( 132 | POGOProtos.parseWithUnknown(key, "POGOProtos.Networking.Envelopes.Signature") 133 | ); 134 | } -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:sshd] 5 | command=/usr/sbin/sshd -D 6 | autorestart=true 7 | 8 | [program:mysql] 9 | command=/usr/bin/pidproxy /var/run/mysqld/mysqld.pid /usr/sbin/mysqld 10 | autorestart=true 11 | -------------------------------------------------------------------------------- /updater.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import fse from "fs-extra"; 3 | import git from "nodegit"; 4 | import dirTree from "directory-tree"; 5 | 6 | let tmpDir = "./tmp"; 7 | let cloneDir = JSON.parse(fs.readFileSync("./package.json")).repository.url; 8 | 9 | let ignore = [".git", "cfg.js", "updater.js"]; 10 | 11 | function skip() { 12 | return new Promise((resolve) => { 13 | fse.removeSync(tmpDir); 14 | console.log(`Skipped update to version ${newVersion}`); 15 | resolve(); 16 | }); 17 | } 18 | 19 | function updateProject() { 20 | return new Promise((resolve) => { 21 | let newFiles = dirTree("./tmp"); 22 | recurse(newFiles.children); 23 | resolve(); 24 | }); 25 | } 26 | 27 | function recurse(parent) { 28 | for (let key in parent) { 29 | let name = parent[key].name; 30 | if (ignore.includes(name)) continue; 31 | let isDir = parent[key].hasOwnProperty("children"); 32 | try { 33 | if (!isDir) { 34 | fse.outputFileSync(parent[key].path.substring(4), fs.readFileSync(parent[key].path)); 35 | } 36 | else { 37 | recurse(parent[key].children); 38 | } 39 | } catch (e) { console.log(e); } 40 | }; 41 | } 42 | 43 | ((() => new Promise((resolve) => { 44 | console.log("Be aware about the updater is experimental!"); 45 | console.log("Preparing to update.."); 46 | setTimeout(() => { 47 | fse.removeSync(tmpDir); 48 | let currentVersion = JSON.parse(fs.readFileSync("./package.json")).version; 49 | console.log(`Your current version is ${currentVersion}!`); 50 | setTimeout(() => { 51 | console.log("Fetching latest version.."); 52 | git.Clone(cloneDir, tmpDir).then((res, rofl) => { 53 | let newVersion = null; 54 | try { 55 | newVersion = JSON.parse(fs.readFileSync(`${tmpDir}/package.json`)).version; 56 | } catch (e) { 57 | console.log("Version check failed!"); 58 | return resolve(); 59 | } 60 | if (currentVersion === newVersion) { 61 | console.log(`You are already running the latest version ${currentVersion}!`); 62 | skip().then(resolve); 63 | return void 0; 64 | } 65 | else if (currentVersion > newVersion) { 66 | console.log(`Your version ${currentVersion} is newer than ${newVersion}!`); 67 | skip().then(resolve); 68 | return void 0; 69 | } 70 | console.log(`Latest version is ${newVersion}`); 71 | console.log(`Updating to version ${newVersion}`); 72 | updateProject().then(() => { 73 | fse.removeSync(tmpDir); 74 | console.log("Update successfully completed, please restart!"); 75 | resolve(); 76 | }); 77 | }); 78 | }, 2e3); 79 | 80 | }, 1e3); 81 | })))(); --------------------------------------------------------------------------------