├── CONTRIBUTING.md ├── LICENSE.md ├── README.md └── www ├── assets ├── css │ ├── gallery.css │ └── index.css ├── img │ ├── favicon.png │ ├── feed.png │ └── split.png └── js │ └── main_out.js ├── checkdir.php ├── include └── gallery.php ├── index.html └── skins └── doge.png /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are appreciated in the form of pull requests. However, to maintain code readability and maintainability, some guidelines have been set. *Your pull request will likely be rejected if it does not merge these guidelines, so please read them carefully.* 3 | 4 | ### Style 5 | * Although semicolons are optional in JavaScript, please use a semicolon after every statement that would normally use a semicolon in other languages 6 | * Tabs should be replaced with 4 spaces. 7 | * New classes should have their constructor's name explicitly specified, and classes should usually be specified as one per file (with rare exceptions). However, class methods should be anonymously defined with no name. Example: 8 | ```js 9 | function ClassName(argument) { 10 | this.argument = argument; 11 | } 12 | 13 | module.exports = ClassName; 14 | 15 | ClassName.prototype.method = function() { 16 | ... 17 | } 18 | ``` 19 | * Unix-style line endings should be used (`\n`). 20 | * Please leave a blank line at the end of each file. 21 | * Conditional/loop statements (`if`, `for`, `while`, etc.) should always use braces, and the opening brace should be placed on the same line as the statement. 22 | * There should be a space after a conditional/loop statement and before the condition, as well as a space after the condition and before the brace. Example: 23 | ```js 24 | // Good 25 | if (condition) { 26 | ... 27 | } 28 | 29 | // Bad 30 | if(condition) { 31 | ... 32 | } 33 | 34 | if(condition){ 35 | ... 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2015 Devin Ryan 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this project except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | # Cigar 4 | An unofficial Agar.io client used to run with Ogar based servers. Allows you to connect to an Ogar based server and have the abilities of the new protocol in a smaller and cleaner client. 5 | 6 | ## Obtaining and Using 7 | Due to parts of Cigar require the use of PHP, you will need to have a web environment with PHP for parts to work. So the project will have to be installed on a webserver or localhost. 8 | 9 | You will need a webserver capable of running PHP only, also it cannot be run off file:// due to cross-origin blocks. 10 | 11 | ### Now using Protocol 6 12 | This version of the client is using the new-ish protocol 6. Uses around 8% less packet size than before. As well, you can use hidden skin setting with the angled brackets, if the server supports it. 13 | 14 | ## Recommended Servers 15 | This client can be used with any server that is built with the same protocol and connect with it properly. However, the recommended server that we are building for and works best with Cigar to get all the features out of it with is [MultiOgar-Edited](https://github.com/Megabyte918/MultiOgar-Edited). 16 | 17 | ## Configuring Cigar 18 | ### Adding More Server Dropdowns 19 | The connection method on Cigar works through the function of 20 | 21 | ```javascript 22 | setserver("IP:PORT"); 23 | ``` 24 | 25 | To add another server, you can call any element that has the main_out.js called to run the function and the server will change within the canvas. To add another dropdown that will switch to your server, add the following line inside of the existing dropdown box. 26 | 27 | ```html 28 | 29 | ``` 30 | 31 | Replace IP:PORT with the ones that are corresponding with your server, you can also change the NAME to anything you like. 32 | 33 | ### Changing Skins Folder 34 | The skins folder can be changed to any folder that is located on your web server. If you wish to move the skins folder onto another domain, please do note that the checkdir.php file might stop working and that will break skins from showing up in game. 35 | 36 | To change the skin folder on the set webserver, you will need to update 3 files. 37 | 38 | `www/assets/js/main_out.js`: 39 | *Remember, folder location here is relative to www/index.html* 40 | 41 | ```javascript 42 | SKIN_URL = "./skins/", // edit me 43 | ``` 44 | 45 | `www/checkdir.php`: 46 | *Remember, folder location here is relative to www/index.html* 47 | 48 | ```php 49 | $images = glob('./skins/*.{png}', GLOB_BRACE); # <-- edit the ./skins/ part 50 | ``` 51 | 52 | `www/include/gallery.php`: 53 | *Remember, the first variable is relative to www/include/gallery.php, and the second one to www/index.html* 54 | 55 | ```php 56 | # Skin directory relative to include/gallery.php (this file) 57 | $skindir = "../skins/"; # <-- edit me 58 | 59 | # Skin directory relative to index.html 60 | $skindirhtml = "./skins/"; # <-- edit me 61 | ``` 62 | 63 | **Be sure that all your skins are in .PNG format and that all URLs end with a `/`**. 64 | 65 | ## Support 66 | You can get support from the community and developers by adding issues or suggestions in the issues tab at this repository. However please do note, **if you remove the backlink to Cigar on your project, we will NOT provide support for your website.** 67 | 68 | ## Contributing 69 | Please see [CONTRIBUTING.md](https://github.com/CigarProject/Cigar/blob/master/CONTRIBUTING.md) for contribution guidelines. 70 | 71 | ## License 72 | Please see [LICENSE.md](https://github.com/CigarProject/Cigar/blob/master/LICENSE.md). 73 | -------------------------------------------------------------------------------- /www/assets/css/gallery.css: -------------------------------------------------------------------------------- 1 | .gallery { 2 | width: 64%; 3 | float: right; 4 | } 5 | .skin { 6 | list-style-type: none; 7 | margin-right: 10px; 8 | margin-bottom: 10px; 9 | float: left; 10 | max-width: 150px; 11 | } 12 | .circular { 13 | width: 150px; 14 | height: 150px; 15 | border-radius: 75px; 16 | -webkit-border-radius: 75px; 17 | -moz-border-radius: 75px; 18 | background-repeat: no-repeat; 19 | box-shadow: 0 0 8px rgba(0, 0, 0, .8); 20 | -webkit-box-shadow: 0 0 8px rgba(0, 0, 0, .8); 21 | -moz-box-shadow: 0 0 8px rgba(0, 0, 0, .8); 22 | background-size: 150px 150px; 23 | cursor: pointer; 24 | } 25 | .circular:hover { 26 | box-shadow: 0 0 12px rgba(0, 0, 0, .8); 27 | -webkit-box-shadow: 0 0 12px rgba(0, 0, 0, .8); 28 | -moz-box-shadow: 0 0 12px rgba(0, 0, 0, .8); 29 | -webkit-transition: all 0.2s linear; 30 | -o-transition: all 0.2s linear; 31 | -moz-transition: all 0.2s linear; 32 | -ms-transition: all 0.2s linear; 33 | transition: all 0.2s linear; 34 | } 35 | .skin > .title { 36 | cursor: pointer; 37 | text-align: center; 38 | } 39 | .skin > .title:hover { 40 | text-shadow: 0 0 2px rgba(0, 0, 0, .8); 41 | -webkit-transition: all 0.2s linear; 42 | -o-transition: all 0.2s linear; 43 | -moz-transition: all 0.2s linear; 44 | -ms-transition: all 0.2s linear; 45 | transition: all 0.2s linear; 46 | } 47 | .imgDescription { 48 | text-align: center; 49 | position: relative; 50 | top: 0; 51 | bottom: 0; 52 | left: 0; 53 | right: 0; 54 | background: rgba(29, 106, 154, 0.72); 55 | color: #fff; 56 | visibility: hidden; 57 | opacity: 0; 58 | -webkit-transition: visibility opacity 0.2s; 59 | } 60 | .circular:hover .imgDescription { 61 | visibility: visible; 62 | opacity: 1; 63 | } -------------------------------------------------------------------------------- /www/assets/css/index.css: -------------------------------------------------------------------------------- 1 | /* GLOBAL */ 2 | 3 | body { 4 | padding: 0; 5 | margin: 0; 6 | overflow: hidden; 7 | } 8 | 9 | /* IDs */ 10 | 11 | #overlays { 12 | display:none; 13 | position: absolute; 14 | left: 0; 15 | right: 0; 16 | top: 0; 17 | bottom: 0; 18 | background-color: 19 | rgba(0,0,0,0.5); 20 | z-index: 200; 21 | } 22 | 23 | #title { 24 | padding: 10px; 25 | } 26 | 27 | #canvas { 28 | position: absolute; 29 | left: 0; 30 | right: 0; 31 | top: 0; 32 | bottom: 0; 33 | width: 100%; 34 | height: 100%; 35 | } 36 | 37 | #connecting { 38 | display:none; 39 | position: absolute; 40 | left: 0; 41 | right: 0; 42 | top: 0; 43 | bottom: 0; 44 | z-index: 100; 45 | background-color: rgba(0,0,0,0.5); 46 | } 47 | 48 | #nick { 49 | width: 65%; 50 | float: left; 51 | } 52 | 53 | #gamemode { 54 | width: 33%; 55 | float: right; 56 | } 57 | 58 | #helloDialog { 59 | width: 400px; 60 | background-color: #FFFFFF; 61 | margin: 10px auto; 62 | border-radius: 15px; 63 | padding: 5px 15px 5px 15px; 64 | position: absolute; 65 | top: 50%; 66 | left: 50%; 67 | margin-right: -50%; 68 | -webkit-transform: translate(-50%, -50%); 69 | -ms-transform: translate(-50%, -50%); 70 | transform: translate(-50%, -50%); 71 | } 72 | 73 | #chat_textbox { 74 | -webkit-transition: all .5s ease-in-out; 75 | -moz-transition: all .5s ease-in-out; 76 | -o-transition: all .5s ease-in-out; 77 | transition: all .5s ease-in-out; 78 | position: absolute; 79 | z-index: 1; 80 | bottom: 10px; 81 | background: rgba(0, 0, 0, .2); 82 | border: 0px; 83 | outline: none; 84 | color: #FFF; 85 | height: 30px; 86 | text-indent: 12px; 87 | left: 10px; 88 | width: 300px; 89 | } 90 | 91 | #chat_textbox:focus { 92 | background: rgba(0, 0, 0, .5); 93 | } 94 | 95 | #footer { 96 | text-align: center; 97 | margin-bottom: 10px; 98 | margin-top: -10px; 99 | } 100 | 101 | #play-btn { 102 | width: 85%; 103 | float: left; 104 | } 105 | 106 | #settings-btn { 107 | width: 13%; 108 | float: right; 109 | } 110 | 111 | #spectate-btn { 112 | margin-top: -5px; 113 | } 114 | 115 | /* CLASSES */ 116 | 117 | .checkbox label { 118 | margin-right: 10px; 119 | } 120 | 121 | .mb-10 { 122 | margin-bottom: 10px; 123 | } 124 | 125 | .loader { 126 | border: 6px solid #f3f3f3; 127 | border-top: 6px solid #3498db; 128 | border-radius: 50%; 129 | width: 40px; 130 | height: 40px; 131 | animation: spin 2s linear infinite; 132 | } 133 | 134 | .center { 135 | display: table; 136 | margin: 0 auto; 137 | } 138 | 139 | @keyframes spin { 140 | 0% { transform: rotate(0deg); } 141 | 100% { transform: rotate(360deg); } 142 | } -------------------------------------------------------------------------------- /www/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luka967/Cigar/b31a9993536a3e5e72ab303ec6493d372ecaa2bc/www/assets/img/favicon.png -------------------------------------------------------------------------------- /www/assets/img/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luka967/Cigar/b31a9993536a3e5e72ab303ec6493d372ecaa2bc/www/assets/img/feed.png -------------------------------------------------------------------------------- /www/assets/img/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Luka967/Cigar/b31a9993536a3e5e72ab303ec6493d372ecaa2bc/www/assets/img/split.png -------------------------------------------------------------------------------- /www/assets/js/main_out.js: -------------------------------------------------------------------------------- 1 | (function(wHandle, wjQuery) { 2 | if (navigator.appVersion.indexOf("MSIE") != -1) 3 | alert("You're using a pretty old browser, some parts of the website might not work properly."); 4 | 5 | Date.now || (Date.now = function() { 6 | return (+new Date).getTime(); 7 | }); 8 | var LOAD_START = Date.now(); 9 | Array.prototype.peek = function() { 10 | return this[this.length - 1]; 11 | }; 12 | Array.prototype.remove = function(a) { 13 | var i = this.indexOf(a); 14 | if (i !== -1) this.splice(i, 1); 15 | return i !== -1; 16 | }; 17 | function bytesToColor(r, g, b) { 18 | var r1 = ("00" + (~~r).toString(16)).slice(-2); 19 | var g1 = ("00" + (~~g).toString(16)).slice(-2); 20 | var b1 = ("00" + (~~b).toString(16)).slice(-2); 21 | return `#${r1}${g1}${b1}`; 22 | } 23 | function colorToBytes(color) { 24 | if (color.length === 4) 25 | return { r: parseInt(color[1] + color[1], 16), g: parseInt(color[2] + color[2], 16), b: parseInt(color[3] + color[3], 16) }; 26 | else if (color.length === 7) 27 | return { r: parseInt(color[1] + color[2], 16), g: parseInt(color[3] + color[4], 16), b: parseInt(color[5] + color[6], 16) }; 28 | throw new Error(`invalid color ${color}`); 29 | } 30 | function darkenColor(color) { 31 | var a = colorToBytes(color); 32 | return bytesToColor(a.r * .9, a.g * .9, a.b * .9); 33 | } 34 | function cleanupObject(object) { 35 | for (var i in object) 36 | delete object[i]; 37 | } 38 | var __buf = new DataView(new ArrayBuffer(8)); 39 | function Writer(littleEndian) { 40 | this._e = littleEndian; 41 | this.reset(); 42 | return this; 43 | } 44 | Writer.prototype = { 45 | writer: true, 46 | reset: function(littleEndian) { 47 | this._b = []; 48 | this._o = 0; 49 | }, 50 | setUint8: function(a) { 51 | if (a >= 0 && a < 256) this._b.push(a); 52 | return this; 53 | }, 54 | setInt8: function(a) { 55 | if (a >= -128 && a < 128) this._b.push(a); 56 | return this; 57 | }, 58 | setUint16: function(a) { 59 | __buf.setUint16(0, a, this._e); 60 | this._move(2); 61 | return this; 62 | }, 63 | setInt16: function(a) { 64 | __buf.setInt16(0, a, this._e); 65 | this._move(2); 66 | return this; 67 | }, 68 | setUint32: function(a) { 69 | __buf.setUint32(0, a, this._e); 70 | this._move(4); 71 | return this; 72 | }, 73 | setInt32: function(a) { 74 | __buf.setInt32(0, a, this._e); 75 | this._move(4); 76 | return this; 77 | }, 78 | setFloat32: function(a) { 79 | __buf.setFloat32(0, a, this._e); 80 | this._move(4); 81 | return this; 82 | }, 83 | setFloat64: function(a) { 84 | __buf.setFloat64(0, a, this._e); 85 | this._move(8); 86 | return this; 87 | }, 88 | _move: function(b) { 89 | for (var i = 0; i < b; i++) this._b.push(__buf.getUint8(i)); 90 | }, 91 | setStringUTF8: function(s) { 92 | var bytesStr = unescape(encodeURIComponent(s)); 93 | for (var i = 0, l = bytesStr.length; i < l; i++) this._b.push(bytesStr.charCodeAt(i)); 94 | this._b.push(0); 95 | return this; 96 | }, 97 | build: function() { 98 | return new Uint8Array(this._b); 99 | } 100 | }; 101 | function Reader(view, offset, littleEndian) { 102 | this._e = littleEndian; 103 | if (view) this.repurpose(view, offset); 104 | } 105 | Reader.prototype = { 106 | reader: true, 107 | repurpose: function(view, offset) { 108 | this.view = view; 109 | this._o = offset || 0; 110 | }, 111 | getUint8: function() { 112 | return this.view.getUint8(this._o++, this._e); 113 | }, 114 | getInt8: function() { 115 | return this.view.getInt8(this._o++, this._e); 116 | }, 117 | getUint16: function() { 118 | return this.view.getUint16((this._o += 2) - 2, this._e); 119 | }, 120 | getInt16: function() { 121 | return this.view.getInt16((this._o += 2) - 2, this._e); 122 | }, 123 | getUint32: function() { 124 | return this.view.getUint32((this._o += 4) - 4, this._e); 125 | }, 126 | getInt32: function() { 127 | return this.view.getInt32((this._o += 4) - 4, this._e); 128 | }, 129 | getFloat32: function() { 130 | return this.view.getFloat32((this._o += 4) - 4, this._e); 131 | }, 132 | getFloat64: function() { 133 | return this.view.getFloat64((this._o += 8) - 8, this._e); 134 | }, 135 | getStringUTF8: function() { 136 | var s = "", b; 137 | while ((b = this.view.getUint8(this._o++)) !== 0) s += String.fromCharCode(b); 138 | 139 | return decodeURIComponent(escape(s)); 140 | } 141 | }; 142 | var log = { 143 | verbosity: 4, 144 | error: function(a) { if (log.verbosity <= 0) return; console.error(a); }, 145 | warn: function(a) { if (log.verbosity <= 1) return; console.warn(a); }, 146 | info: function(a) { if (log.verbosity <= 2) return; console.info(a); }, 147 | debug: function(a) { if (log.verbosity <= 3) return; console.debug(a); } 148 | }; 149 | 150 | var wsUrl = null, 151 | SKIN_URL = "./skins/", 152 | USE_HTTPS = "https:" == wHandle.location.protocol, 153 | PI_2 = Math.PI * 2, 154 | SEND_254 = new Uint8Array([254, 6, 0, 0, 0]), 155 | SEND_255 = new Uint8Array([255, 1, 0, 0, 0]), 156 | UINT8_CACHE = { 157 | 1: new Uint8Array([1]), 158 | 17: new Uint8Array([17]), 159 | 21: new Uint8Array([21]), 160 | 18: new Uint8Array([18]), 161 | 19: new Uint8Array([19]), 162 | 22: new Uint8Array([22]), 163 | 23: new Uint8Array([23]), 164 | 24: new Uint8Array([24]), 165 | 254: new Uint8Array([254]) 166 | }; 167 | 168 | function wsCleanup() { 169 | if (!ws) return; 170 | log.debug("ws cleanup trigger"); 171 | ws.onopen = null; 172 | ws.onmessage = null; 173 | ws.close(); 174 | ws = null; 175 | } 176 | function wsInit(url) { 177 | if (ws) { 178 | log.debug("ws init on existing conn"); 179 | wsCleanup(); 180 | } 181 | wjQuery("#connecting").show(); 182 | ws = new WebSocket(`ws${USE_HTTPS ? "s" : ""}://${wsUrl = url}`); 183 | ws.binaryType = "arraybuffer"; 184 | ws.onopen = wsOpen; 185 | ws.onmessage = wsMessage; 186 | ws.onerror = wsError; 187 | ws.onclose = wsClose; 188 | } 189 | function wsOpen() { 190 | disconnectDelay = 1000; 191 | wjQuery("#connecting").hide(); 192 | wsSend(SEND_254); 193 | wsSend(SEND_255); 194 | log.debug(`ws connected, using https: ${USE_HTTPS}`); 195 | } 196 | function wsError(error) { 197 | log.warn(error); 198 | } 199 | function wsClose(e) { 200 | log.debug(`ws disconnected ${e.code} '${e.reason}'`); 201 | wsCleanup(); 202 | gameReset(); 203 | setTimeout(function() { 204 | if (ws && ws.readyState === 1) return; 205 | wsInit(wsUrl); 206 | }, disconnectDelay *= 1.5); 207 | } 208 | function wsSend(data) { 209 | if (!ws) return; 210 | if (ws.readyState !== 1) return; 211 | if (data.build) ws.send(data.build()); 212 | else ws.send(data); 213 | } 214 | function wsMessage(data) { 215 | syncUpdStamp = Date.now(); 216 | var reader = new Reader(new DataView(data.data), 0, true); 217 | var packetId = reader.getUint8(); 218 | switch (packetId) { 219 | case 0x10: // update nodes 220 | var killer, killed, id, node, x, y, s, flags, cell, 221 | updColor, updName, updSkin, count, color, name, skin; 222 | 223 | // consume records 224 | count = reader.getUint16(); 225 | for (var i = 0; i < count; i++) { 226 | killer = reader.getUint32(); 227 | killed = reader.getUint32(); 228 | if (!cells.byId.hasOwnProperty(killer) || !cells.byId.hasOwnProperty(killed)) 229 | continue; 230 | cells.byId[killed].destroy(killer); 231 | } 232 | 233 | // update records 234 | while (true) { 235 | id = reader.getUint32(); 236 | if (id === 0) break; 237 | 238 | x = reader.getInt32(); 239 | y = reader.getInt32(); 240 | s = reader.getUint16(); 241 | 242 | flags = reader.getUint8(); 243 | updColor = !!(flags & 0x02); 244 | updName = !!(flags & 0x08); 245 | updSkin = !!(flags & 0x04); 246 | color = updColor ? bytesToColor(reader.getUint8(), reader.getUint8(), reader.getUint8()) : null; 247 | skin = updSkin ? reader.getStringUTF8() : null; 248 | name = updName ? reader.getStringUTF8() : null; 249 | 250 | if (cells.byId.hasOwnProperty(id)) { 251 | cell = cells.byId[id]; 252 | cell.update(syncUpdStamp); 253 | cell.updated = syncUpdStamp; 254 | cell.ox = cell.x; 255 | cell.oy = cell.y; 256 | cell.os = cell.s; 257 | cell.nx = x; 258 | cell.ny = y; 259 | cell.ns = s; 260 | if (color) cell.setColor(color); 261 | if (skin) cell.setSkin(skin); 262 | if (name) cell.setName(name); 263 | } else { 264 | cell = new Cell(id, x, y, s, name, color, skin, flags); 265 | cells.byId[id] = cell; 266 | cells.list.push(cell); 267 | } 268 | } 269 | // dissapear records 270 | count = reader.getUint16(); 271 | for (i = 0; i < count; i++) { 272 | killed = reader.getUint32(); 273 | if (cells.byId.hasOwnProperty(killed) && !cells.byId[killed].destroyed) 274 | cells.byId[killed].destroy(null); 275 | } 276 | break; 277 | case 0x11: // update pos 278 | targetX = reader.getFloat32(); 279 | targetY = reader.getFloat32(); 280 | targetZ = reader.getFloat32(); 281 | break; 282 | case 0x12: // clear all 283 | for (var i in cells.byId) 284 | cells.byId[i].destroy(null); 285 | case 0x14: // clear my cells 286 | cells.mine = []; 287 | break; 288 | case 0x15: // draw line 289 | log.warn("got packer 0x15 (draw line) which is unsupported"); 290 | break; 291 | case 0x20: // new cell 292 | cells.mine.push(reader.getUint32()); 293 | break; 294 | case 0x30: // text list 295 | leaderboard.items = []; 296 | leaderboard.type = "text"; 297 | 298 | var count = reader.getUint32(); 299 | for (i = 0; i < count; ++i) 300 | leaderboard.items.push(reader.getStringUTF8()); 301 | drawLeaderboard(); 302 | break; 303 | case 0x31: // ffa list 304 | leaderboard.items = []; 305 | leaderboard.type = "ffa"; 306 | 307 | var count = reader.getUint32(); 308 | for (i = 0; i < count; ++i) 309 | leaderboard.items.push({ 310 | me: !!reader.getUint32(), 311 | name: reader.getStringUTF8() || "An unnamed cell" 312 | }); 313 | drawLeaderboard(); 314 | break; 315 | case 0x32: // pie chart 316 | leaderboard.items = []; 317 | leaderboard.type = "pie"; 318 | 319 | var count = reader.getUint32(); 320 | for (i = 0; i < count; ++i) 321 | leaderboard.items.push(reader.getFloat32()); 322 | drawLeaderboard(); 323 | break; 324 | case 0x40: // set border 325 | border.left = reader.getFloat64(); 326 | border.top = reader.getFloat64(); 327 | border.right = reader.getFloat64(); 328 | border.bottom = reader.getFloat64(); 329 | border.width = border.right - border.left; 330 | border.height = border.bottom - border.top; 331 | border.centerX = (border.left + border.right) / 2; 332 | border.centerY = (border.top + border.bottom) / 2; 333 | if (data.data.byteLength === 33) break; 334 | if (!mapCenterSet) { 335 | mapCenterSet = true; 336 | cameraX = targetX = border.centerX; 337 | cameraY = targetY = border.centerY; 338 | cameraZ = targetZ = 1; 339 | } 340 | reader.getUint32(); // game type 341 | if (!/MultiOgar|OgarII/.test(reader.getStringUTF8()) || stats.pingLoopId) break; 342 | stats.pingLoopId = setInterval(function() { 343 | wsSend(UINT8_CACHE[254]); 344 | stats.pingLoopStamp = Date.now(); 345 | }, 2000); 346 | break; 347 | case 0x63: // chat message 348 | var flags = reader.getUint8(); 349 | var color = bytesToColor(reader.getUint8(), reader.getUint8(), reader.getUint8()); 350 | 351 | var name = reader.getStringUTF8().trim(); 352 | var reg = /\{([\w]+)\}/.exec(name); 353 | if (reg) name = name.replace(reg[0], "").trim(); 354 | var message = reader.getStringUTF8(); 355 | 356 | var server = !!(flags & 0x80), 357 | admin = !!(flags & 0x40), 358 | mod = !!(flags & 0x20); 359 | 360 | if (server && name !== "SERVER") name = "[SERVER] " + name; 361 | if (admin) name = "[ADMIN] " + name; 362 | if (mod) name = "[MOD] " + name; 363 | var wait = Math.max(3000, 1000 + message.length * 150); 364 | chat.waitUntil = syncUpdStamp - chat.waitUntil > 1000 ? syncUpdStamp + wait : chat.waitUntil + wait; 365 | chat.messages.push({ 366 | server: server, 367 | admin: admin, 368 | mod: mod, 369 | color: color, 370 | name: name, 371 | message: message, 372 | time: syncUpdStamp 373 | }); 374 | drawChat(); 375 | break; 376 | case 0xFE: // server stat 377 | stats.info = JSON.parse(reader.getStringUTF8()); 378 | stats.latency = syncUpdStamp - stats.pingLoopStamp; 379 | drawStats(); 380 | break; 381 | default: 382 | // invalid packet 383 | wsCleanup(); 384 | break; 385 | } 386 | } 387 | function sendMouseMove(x, y) { 388 | var writer = new Writer(true); 389 | writer.setUint8(0x10); 390 | writer.setUint32(x); 391 | writer.setUint32(y); 392 | writer._b.push(0, 0, 0, 0); 393 | wsSend(writer); 394 | } 395 | function sendPlay(name) { 396 | log.debug("play trigger"); 397 | var writer = new Writer(true); 398 | writer.setUint8(0x00); 399 | writer.setStringUTF8(name); 400 | wsSend(writer); 401 | } 402 | function sendChat(text) { 403 | var writer = new Writer(); 404 | writer.setUint8(0x63); 405 | writer.setUint8(0); 406 | writer.setStringUTF8(text); 407 | wsSend(writer); 408 | } 409 | 410 | function gameReset() { 411 | cleanupObject(cells); 412 | cleanupObject(border); 413 | cleanupObject(leaderboard); 414 | cleanupObject(chat); 415 | cleanupObject(stats); 416 | chat.messages = []; 417 | leaderboard.items = []; 418 | cells.mine = []; 419 | cells.byId = { }; 420 | cells.list = []; 421 | cameraX = cameraY = targetX = targetY = 0; 422 | cameraZ = targetZ = 1; 423 | mapCenterSet = false; 424 | } 425 | 426 | var cells = Object.create({ 427 | mine: [], 428 | byId: { }, 429 | list: [], 430 | }); 431 | var border = Object.create({ 432 | left: -2000, 433 | right: 2000, 434 | top: -2000, 435 | bottom: 2000, 436 | width: 4000, 437 | height: 4000, 438 | centerX: -1, 439 | centerY: -1 440 | }); 441 | var leaderboard = Object.create({ 442 | type: NaN, 443 | items: null, 444 | canvas: document.createElement("canvas"), 445 | teams: ["#F33", "#3F3", "#33F"] 446 | }); 447 | var chat = Object.create({ 448 | messages: [], 449 | waitUntil: 0, 450 | canvas: document.createElement("canvas"), 451 | visible: false, 452 | }); 453 | var stats = Object.create({ 454 | framesPerSecond: 0, 455 | latency: NaN, 456 | supports: null, 457 | info: null, 458 | pingLoopId: NaN, 459 | pingLoopStamp: null, 460 | canvas: document.createElement("canvas"), 461 | visible: false, 462 | score: NaN, 463 | maxScore: 0 464 | }); 465 | 466 | var ws = null; 467 | var wsUrl = null; 468 | var disconnectDelay = 1000; 469 | 470 | var syncUpdStamp = Date.now(); 471 | var syncAppStamp = Date.now(); 472 | 473 | var mainCanvas = null; 474 | var mainCtx = null; 475 | var knownSkins = { }; 476 | var loadedSkins = { }; 477 | var escOverlayShown = false; 478 | var isTyping = false; 479 | var chatBox = null; 480 | var mapCenterSet = false; 481 | var cameraX = 0; 482 | var cameraY = 0; 483 | var cameraZ = 1; 484 | var cameraZInvd = 1; 485 | var targetX = 0; 486 | var targetY = 0; 487 | var targetZ = 1; 488 | var viewMult = 1; 489 | var mouseX = NaN; 490 | var mouseY = NaN; 491 | var mouseZ = 1; 492 | 493 | var settings = { 494 | mobile: "createTouch" in document, 495 | showMass: false, 496 | showNames: true, 497 | showLeaderboard: true, 498 | showChat: true, 499 | showGrid: true, 500 | showTextOutline: true, 501 | showColor: true, 502 | showSkins: true, 503 | showMinimap: true, 504 | darkTheme: false, 505 | allowGETipSet: false 506 | }; 507 | var pressed = { 508 | space: false, 509 | w: false, 510 | e: false, 511 | r: false, 512 | t: false, 513 | p: false, 514 | q: false, 515 | esc: false 516 | }; 517 | 518 | if (null !== wHandle.localStorage) { 519 | wjQuery(window).load(function() { 520 | wjQuery(".save").each(function() { 521 | var id = wjQuery(this).data("box-id"); 522 | var value = wHandle.localStorage.getItem("checkbox-" + id); 523 | if (value && value == "true" && 0 != id) { 524 | wjQuery(this).prop("checked", "true"); 525 | wjQuery(this).trigger("change"); 526 | } else if (id == 0 && value != null) 527 | wjQuery(this).val(value); 528 | }); 529 | wjQuery(".save").change(function() { 530 | var id = wjQuery(this).data("box-id"); 531 | var value = (id == 0) ? wjQuery(this).val() : wjQuery(this).prop("checked"); 532 | wHandle.localStorage.setItem("checkbox-" + id, value); 533 | }); 534 | }); 535 | } 536 | wjQuery.ajax({ 537 | type: "POST", 538 | dataType: "json", 539 | url: "checkdir.php", 540 | data: { "action": "getSkins" }, 541 | success: function(data) { 542 | var stamp = Date.now(); 543 | response = JSON.parse(data.names); 544 | for (var i = 0; i < response.length; i++) 545 | knownSkins[response[i]] = stamp; 546 | for (var i in knownSkins) 547 | if (knownSkins[i] !== stamp) delete knownSkins[i]; 548 | } 549 | }); 550 | 551 | function hideESCOverlay() { 552 | escOverlayShown = false; 553 | wjQuery("#overlays").hide(); 554 | } 555 | function showESCOverlay() { 556 | escOverlayShown = true; 557 | wjQuery("#overlays").fadeIn(300); 558 | } 559 | 560 | function toCamera(ctx) { 561 | ctx.translate(mainCanvas.width / 2, mainCanvas.height / 2); 562 | scaleForth(ctx); 563 | ctx.translate(-cameraX, -cameraY); 564 | } 565 | function scaleForth(ctx) { ctx.scale(cameraZ, cameraZ); } 566 | function scaleBack(ctx) { ctx.scale(cameraZInvd, cameraZInvd); } 567 | function fromCamera(ctx) { 568 | ctx.translate(cameraX, cameraY); 569 | scaleBack(ctx); 570 | ctx.translate(-mainCanvas.width / 2, -mainCanvas.height / 2); 571 | } 572 | 573 | function drawChat() { 574 | if (chat.messages.length === 0 && settings.showChat) 575 | return chat.visible = false; 576 | chat.visible = true; 577 | var canvas = chat.canvas; 578 | var ctx = canvas.getContext("2d"); 579 | var latestMessages = chat.messages.slice(-15); 580 | var lines = []; 581 | for (var i = 0, len = latestMessages.length; i < len; i++) 582 | lines.push([ 583 | { 584 | text: latestMessages[i].name, 585 | color: latestMessages[i].color 586 | }, { 587 | text: " " + latestMessages[i].message, 588 | color: settings.darkTheme ? "#FFF" : "#000" 589 | } 590 | ]); 591 | var width = 0; 592 | var height = 20 * len + 2; 593 | for (var i = 0; i < len; i++) { 594 | var thisLineWidth = 0; 595 | var complexes = lines[i]; 596 | for (var j = 0; j < complexes.length; j++) { 597 | ctx.font = "18px Ubuntu"; 598 | complexes[j].width = ctx.measureText(complexes[j].text).width; 599 | thisLineWidth += complexes[j].width; 600 | } 601 | width = Math.max(thisLineWidth, width); 602 | } 603 | canvas.width = width; 604 | canvas.height = height; 605 | for (var i = 0; i < len; i++) { 606 | width = 0; 607 | var complexes = lines[i]; 608 | for (var j = 0; j < complexes.length; j++) { 609 | ctx.font = "18px Ubuntu"; 610 | ctx.fillStyle = complexes[j].color; 611 | ctx.fillText(complexes[j].text, width, 20 * (1 + i)); 612 | width += complexes[j].width; 613 | } 614 | } 615 | } 616 | 617 | function drawStats() { 618 | if (!stats.info) return stats.visible = false; 619 | stats.visible = true; 620 | 621 | var canvas = stats.canvas; 622 | var ctx = canvas.getContext("2d"); 623 | ctx.font = "14px Ubuntu"; 624 | var rows = [ 625 | `${stats.info.name} (${stats.info.mode})`, 626 | `${stats.info.playersTotal} / ${stats.info.playersLimit} players`, 627 | `${stats.info.playersAlive} playing`, 628 | `${stats.info.playersSpect} spectating`, 629 | `${(stats.info.update * 2.5).toFixed(1)}% load @ ${prettyPrintTime(stats.info.uptime)}` 630 | ]; 631 | var width = 0; 632 | for (var i = 0; i < rows.length; i++) 633 | width = Math.max(width, 2 + ctx.measureText(rows[i]).width + 2); 634 | canvas.width = width; 635 | canvas.height = rows.length * (14 + 2); 636 | ctx.font = "14px Ubuntu"; 637 | ctx.fillStyle = settings.darkTheme ? "#AAA" : "#555"; 638 | ctx.textBaseline = "top"; 639 | for (var i = 0; i < rows.length; i++) 640 | ctx.fillText(rows[i], 2, -2 + i * (14 + 2)); 641 | } 642 | function prettyPrintTime(seconds) { 643 | seconds = ~~seconds; 644 | var minutes = ~~(seconds / 60); 645 | if (minutes < 1) return "<1 min"; 646 | var hours = ~~(minutes / 60); 647 | if (hours < 1) return minutes + "min"; 648 | var days = ~~(hours / 24); 649 | if (days < 1) return hours + "h"; 650 | return days + "d"; 651 | } 652 | 653 | function drawLeaderboard() { 654 | if (leaderboard.type === NaN) return leaderboard.visible = false; 655 | if (!settings.showNames || leaderboard.items.length === 0) 656 | return leaderboard.visible = false; 657 | leaderboard.visible = true; 658 | var canvas = leaderboard.canvas; 659 | var ctx = canvas.getContext("2d"); 660 | var len = leaderboard.items.length; 661 | 662 | canvas.width = 200; 663 | canvas.height = leaderboard.type !== "pie" ? 60 + 24 * len : 240; 664 | 665 | ctx.globalAlpha = .4; 666 | ctx.fillStyle = "#000"; 667 | ctx.fillRect(0, 0, 200, canvas.height); 668 | 669 | ctx.globalAlpha = 1; 670 | ctx.fillStyle = "#FFF"; 671 | ctx.font = "30px Ubuntu"; 672 | ctx.fillText("Leaderboard", 100 - ctx.measureText("Leaderboard").width / 2, 40); 673 | 674 | if (leaderboard.type === "pie") { 675 | var last = 0; 676 | for (var i = 0; i < len; i++) { 677 | ctx.fillStyle = leaderboard.teams[i]; 678 | ctx.beginPath(); 679 | ctx.moveTo(100, 140); 680 | ctx.arc(100, 140, 80, last, (last += leaderboard.items[i] * PI_2), false); 681 | ctx.closePath(); 682 | ctx.fill(); 683 | } 684 | } else { 685 | var text, isMe = false, w, start; 686 | ctx.font = "20px Ubuntu"; 687 | for (var i = 0; i < len; i++) { 688 | if (leaderboard.type === "text") 689 | text = leaderboard.items[i]; 690 | else 691 | text = leaderboard.items[i].name, 692 | isMe = leaderboard.items[i].me; 693 | 694 | // replace {skin} with empty string 695 | var reg = /\{([\w]+)\}/.exec(text); 696 | if (reg) text = text.replace(reg[0], "").trim(); 697 | 698 | ctx.fillStyle = isMe ? "#FAA" : "#FFF"; 699 | if (leaderboard.type === "ffa") 700 | text = (i + 1) + ". " + (text || "An unnamed cell"); 701 | var start = ((w = ctx.measureText(text).width) > 200) ? 2 : 100 - w * 0.5; 702 | ctx.fillText(text, start, 70 + 24 * i); 703 | } 704 | } 705 | } 706 | function drawGrid() { 707 | mainCtx.save(); 708 | mainCtx.lineWidth = 1; 709 | mainCtx.strokeStyle = settings.darkTheme ? "#AAA" : "#000"; 710 | mainCtx.globalAlpha = 0.2; 711 | var step = 50, i; 712 | cW = mainCanvas.width / cameraZ, cH = mainCanvas.height / cameraZ, 713 | startLeft = (-cameraX + cW / 2) % step, 714 | startTop = (-cameraY + cH / 2) % step; 715 | 716 | scaleForth(mainCtx); 717 | mainCtx.beginPath(); 718 | for (i = startLeft; i < cW; i += step) { 719 | mainCtx.moveTo(i, 0); 720 | mainCtx.lineTo(i, cH); 721 | } 722 | for (i = startTop; i < cH; i += step) { 723 | mainCtx.moveTo(0, i); 724 | mainCtx.lineTo(cW, i); 725 | } 726 | mainCtx.closePath(); 727 | mainCtx.stroke(); 728 | mainCtx.restore(); 729 | } 730 | function drawMinimap() { 731 | if (border.centerX !== 0 || border.centerY !== 0 || !settings.showMinimap) 732 | // scramble level 2+ makes the minimap unusable 733 | // and is detectable with a non-zero map center 734 | return; 735 | mainCtx.save(); 736 | var targetSize = 200; 737 | var width = targetSize * (border.width / border.height); 738 | var height = targetSize * (border.height / border.width); 739 | var beginX = mainCanvas.width / viewMult - width; 740 | var beginY = mainCanvas.height / viewMult - height; 741 | 742 | mainCtx.fillStyle = "#000"; 743 | mainCtx.globalAlpha = 0.4; 744 | mainCtx.fillRect(beginX, beginY, width, height); 745 | mainCtx.globalAlpha = 1; 746 | 747 | var sectorCount = 5; 748 | var sectorNames = ["ABCDE", "12345"]; 749 | var sectorWidth = width / sectorCount; 750 | var sectorHeight = height / sectorCount; 751 | var sectorNameSize = Math.min(sectorWidth, sectorHeight) / 3; 752 | 753 | mainCtx.fillStyle = settings.darkTheme ? "#666" : "#DDD"; 754 | mainCtx.textBaseline = "middle"; 755 | mainCtx.textAlign = "center"; 756 | mainCtx.font = `${sectorNameSize}px Ubuntu`; 757 | 758 | for (var i = 0; i < sectorCount; i++) { 759 | var x = sectorWidth / 2 + i * sectorWidth; 760 | for (var j = 0; j < sectorCount; j++) { 761 | var y = sectorHeight / 2 + j * sectorHeight; 762 | mainCtx.fillText(`${sectorNames[0][i]}${sectorNames[1][j]}`, beginX + x, beginY + y); 763 | } 764 | } 765 | 766 | var myPosX = beginX + ((cameraX + border.width / 2) / border.width * width); 767 | var myPosY = beginY + ((cameraY + border.height / 2) / border.height * height); 768 | mainCtx.fillStyle = "#FAA"; 769 | mainCtx.beginPath(); 770 | mainCtx.arc(myPosX, myPosY, 5, 0, PI_2, false); 771 | mainCtx.closePath(); 772 | mainCtx.fill(); 773 | 774 | // draw name above user's pos if he has a cell on the screen 775 | var cell = null; 776 | for (var i = 0, l = cells.mine.length; i < l; i++) 777 | if (cells.byId.hasOwnProperty(cells.mine[i])) { 778 | cell = cells.byId[cells.mine[i]]; 779 | break; 780 | } 781 | if (cell !== null) { 782 | mainCtx.fillStyle = settings.darkTheme ? "#DDD" : "#222"; 783 | var textSize = sectorNameSize; 784 | mainCtx.font = `${textSize}px Ubuntu`; 785 | mainCtx.fillText(cell.name, myPosX, myPosY - 7 - textSize / 2); 786 | } 787 | 788 | mainCtx.restore(); 789 | } 790 | 791 | function drawGame() { 792 | stats.framesPerSecond += (1000 / Math.max(Date.now() - syncAppStamp, 1) - stats.framesPerSecond) / 10; 793 | syncAppStamp = Date.now(); 794 | 795 | var drawList = cells.list.slice(0).sort(cellSort); 796 | for (var i = 0, l = drawList.length; i < l; i++) 797 | drawList[i].update(syncAppStamp); 798 | cameraUpdate(); 799 | 800 | mainCtx.save(); 801 | 802 | mainCtx.fillStyle = settings.darkTheme ? "#111" : "#F2FBFF"; 803 | mainCtx.fillRect(0, 0, mainCanvas.width, mainCanvas.height); 804 | if (settings.showGrid) drawGrid(); 805 | 806 | toCamera(mainCtx); 807 | 808 | for (var i = 0, l = drawList.length; i < l; i++) 809 | drawList[i].draw(mainCtx); 810 | 811 | fromCamera(mainCtx); 812 | mainCtx.scale(viewMult, viewMult); 813 | 814 | var height = 2; 815 | mainCtx.fillStyle = settings.darkTheme ? "#FFF" : "#000"; 816 | mainCtx.textBaseline = "top"; 817 | if (!isNaN(stats.score)) { 818 | mainCtx.font = "30px Ubuntu"; 819 | mainCtx.fillText(`Score: ${stats.score}`, 2, height); 820 | height += 30; 821 | } 822 | mainCtx.font = "20px Ubuntu"; 823 | var gameStatsText = `${~~stats.framesPerSecond} FPS`; 824 | if (!isNaN(stats.latency)) gameStatsText += ` ${stats.latency}ms ping`; 825 | mainCtx.fillText(gameStatsText, 2, height); 826 | height += 24; 827 | 828 | if (stats.visible) 829 | mainCtx.drawImage(stats.canvas, 2, height); 830 | if (leaderboard.visible) 831 | mainCtx.drawImage( 832 | leaderboard.canvas, 833 | mainCanvas.width / viewMult - 10 - leaderboard.canvas.width, 834 | 10); 835 | if (chat.visible || isTyping) { 836 | mainCtx.globalAlpha = isTyping ? 1 : Math.max(1000 - syncAppStamp + chat.waitUntil, 0) / 1000; 837 | mainCtx.drawImage( 838 | chat.canvas, 839 | 10 / viewMult, 840 | (mainCanvas.height - 55) / viewMult - chat.canvas.height 841 | ); 842 | mainCtx.globalAlpha = 1; 843 | } 844 | drawMinimap(); 845 | 846 | mainCtx.restore(); 847 | 848 | cacheCleanup(); 849 | wHandle.requestAnimationFrame(drawGame); 850 | } 851 | 852 | function cellSort(a, b) { 853 | return a.s === b.s ? a.id - b.id : a.s - b.s; 854 | } 855 | 856 | function cameraUpdate() { 857 | var myCells = []; 858 | for (var i = 0; i < cells.mine.length; i++) 859 | if (cells.byId.hasOwnProperty(cells.mine[i])) 860 | myCells.push(cells.byId[cells.mine[i]]); 861 | if (myCells.length > 0) { 862 | var x = 0, 863 | y = 0, 864 | s = 0, 865 | score = 0; 866 | for (var i = 0, l = myCells.length; i < l; i++) { 867 | var cell = myCells[i]; 868 | score += ~~(cell.ns * cell.ns / 100); 869 | x += cell.x; 870 | y += cell.y; 871 | s += cell.s; 872 | } 873 | targetX = x / l; 874 | targetY = y / l; 875 | targetZ = Math.pow(Math.min(64 / s, 1), .4); 876 | cameraX += (targetX - cameraX) / 2; 877 | cameraY += (targetY - cameraY) / 2; 878 | stats.score = score; 879 | stats.maxScore = Math.max(stats.maxScore, score); 880 | } else { 881 | stats.score = NaN; 882 | stats.maxScore = 0; 883 | cameraX += (targetX - cameraX) / 20; 884 | cameraY += (targetY - cameraY) / 20; 885 | } 886 | cameraZ += (targetZ * viewMult * mouseZ - cameraZ) / 9; 887 | cameraZInvd = 1 / cameraZ; 888 | } 889 | 890 | function Cell(id, x, y, s, name, color, skin, flags) { 891 | this.id = id; 892 | this.x = this.nx = this.ox = x; 893 | this.y = this.ny = this.oy = y; 894 | this.s = this.ns = this.os = s; 895 | this.setColor(color); 896 | this.setName(name); 897 | this.setSkin(skin); 898 | this.jagged = flags & 0x01 || flags & 0x10; 899 | this.ejected = !!(flags & 0x20); 900 | this.born = syncUpdStamp; 901 | } 902 | Cell.prototype = { 903 | destroyed: false, 904 | id: 0, diedBy: 0, 905 | ox: 0, x: 0, nx: 0, 906 | oy: 0, y: 0, ny: 0, 907 | os: 0, s: 0, ns: 0, 908 | nameSize: 0, drawNameSize: 0, 909 | color: "#FFF", sColor: "#E5E5E5", 910 | skin: null, jagged: false, 911 | born: null, updated: null, dead: null, // timestamps 912 | destroy: function(killerId) { 913 | delete cells.byId[this.id]; 914 | if (cells.mine.remove(this.id) && cells.mine.length === 0) 915 | showESCOverlay(); 916 | this.destroyed = true; 917 | this.dead = syncUpdStamp; 918 | if (killerId && !this.diedBy) 919 | this.diedBy = killerId; 920 | }, 921 | update: function(relativeTime) { 922 | var dt = (relativeTime - this.updated) / 120; 923 | dt = Math.max(Math.min(dt, 1), 0); 924 | if (this.destroyed && Date.now() > this.dead + 200) 925 | cells.list.remove(this); 926 | else if (this.diedBy && cells.byId.hasOwnProperty(this.diedBy)) { 927 | this.nx = cells.byId[this.diedBy].x; 928 | this.ny = cells.byId[this.diedBy].y; 929 | } 930 | this.x = this.ox + (this.nx - this.ox) * dt; 931 | this.y = this.oy + (this.ny - this.oy) * dt; 932 | this.s = this.os + (this.ns - this.os) * dt; 933 | this.nameSize = ~~(~~(Math.max(~~(0.3 * this.ns), 24)) / 3) * 3; 934 | this.drawNameSize = ~~(~~(Math.max(~~(0.3 * this.s), 24)) / 3) * 3; 935 | }, 936 | setName: function(value) { 937 | var nameSkin = /\{([\w\W]+)\}/.exec(value); 938 | if (this.skin === null && nameSkin !== null) { 939 | this.name = value.replace(nameSkin[0], "").trim(); 940 | this.setSkin(nameSkin[1]); 941 | } else this.name = value; 942 | }, 943 | setSkin: function(value) { 944 | this.skin = (value && value[0] === "%" ? value.slice(1) : value) || this.skin; 945 | if (this.skin === null || !knownSkins.hasOwnProperty(this.skin) || loadedSkins[this.skin]) 946 | return; 947 | loadedSkins[this.skin] = new Image(); 948 | loadedSkins[this.skin].src = `${SKIN_URL}${this.skin}.png`; 949 | }, 950 | setColor: function(value) { 951 | if (!value) { log.warn("got no color"); return; } 952 | this.color = value; 953 | this.sColor = darkenColor(value); 954 | }, 955 | draw: function(ctx) { 956 | ctx.save(); 957 | this.drawShape(ctx); 958 | this.drawText(ctx); 959 | ctx.restore(); 960 | }, 961 | drawShape: function(ctx) { 962 | ctx.fillStyle = settings.showColor ? this.color : Cell.prototype.color; 963 | ctx.strokeStyle = settings.showColor ? this.sColor : Cell.prototype.sColor; 964 | ctx.lineWidth = Math.max(~~(this.s / 50), 10); 965 | if (!this.ejected && 20 < this.s) 966 | this.s -= ctx.lineWidth / 2 - 2; 967 | 968 | ctx.beginPath(); 969 | if (this.jagged) { 970 | var pointCount = 120; 971 | var incremental = PI_2 / pointCount; 972 | ctx.moveTo(this.x, this.y + this.s + 3); 973 | for (var i = 1; i < pointCount; i++) { 974 | var angle = i * incremental; 975 | var dist = this.s - 3 + (i % 2 === 0) * 6; 976 | ctx.lineTo( 977 | this.x + dist * Math.sin(angle), 978 | this.y + dist * Math.cos(angle) 979 | ) 980 | } 981 | ctx.lineTo(this.x, this.y + this.s + 3); 982 | } else ctx.arc(this.x, this.y, this.s, 0, PI_2, false); 983 | ctx.closePath(); 984 | 985 | if (this.destroyed) 986 | ctx.globalAlpha = Math.max(200 - Date.now() + this.dead, 0) / 100; 987 | else ctx.globalAlpha = Math.min(Date.now() - this.born, 200) / 100; 988 | 989 | if (!this.ejected && 20 < this.s) 990 | ctx.stroke(); 991 | ctx.fill(); 992 | if (settings.showSkins && this.skin) { 993 | var skin = loadedSkins[this.skin]; 994 | if (skin && skin.complete && skin.width && skin.height) { 995 | ctx.save(); 996 | ctx.clip(); 997 | scaleBack(ctx); 998 | var sScaled = this.s * cameraZ; 999 | ctx.drawImage(skin, 1000 | this.x * cameraZ - sScaled, 1001 | this.y * cameraZ - sScaled, 1002 | sScaled *= 2, sScaled); 1003 | scaleForth(ctx); 1004 | ctx.restore(); 1005 | } 1006 | } 1007 | if (!this.ejected && 20 < this.s) 1008 | this.s += ctx.lineWidth / 2 - 2; 1009 | }, 1010 | drawText: function(ctx) { 1011 | if (this.s < 20 || this.jagged) return; 1012 | if (settings.showMass && (cells.mine.indexOf(this.id) !== -1 || cells.mine.length === 0)) { 1013 | var mass = (~~(this.s * this.s / 100)).toString(); 1014 | if (this.name && settings.showNames) { 1015 | drawText(ctx, false, this.x, this.y, this.nameSize, this.drawNameSize, this.name); 1016 | var y = this.y + Math.max(this.s / 4.5, this.nameSize / 1.5); 1017 | drawText(ctx, true, this.x, y, this.nameSize / 2, this.drawNameSize / 2, mass); 1018 | } else drawText(ctx, true, this.x, this.y, this.nameSize / 2, this.drawNameSize / 2, mass); 1019 | } else if (this.name && settings.showNames) 1020 | drawText(ctx, false, this.x, this.y, this.nameSize, this.drawNameSize, this.name); 1021 | } 1022 | }; 1023 | 1024 | function cacheCleanup() { 1025 | for (var i in cachedNames) { 1026 | for (var j in cachedNames[i]) 1027 | if (syncAppStamp - cachedNames[i][j].accessTime >= 5000) 1028 | delete cachedNames[i][j]; 1029 | if (cachedNames[i] === { }) delete cachedNames[i]; 1030 | } 1031 | for (var i in cachedMass) 1032 | if (syncAppStamp - cachedMass[i].accessTime >= 5000) 1033 | delete cachedMass[i]; 1034 | } 1035 | 1036 | // 2-var draw-stay cache 1037 | var cachedNames = { }; 1038 | var cachedMass = { }; 1039 | 1040 | function drawTextOnto(canvas, ctx, text, size) { 1041 | ctx.font = `${size}px Ubuntu`; 1042 | ctx.lineWidth = settings.showTextOutline ? Math.max(~~(size / 10), 2) : 2; 1043 | canvas.width = ctx.measureText(text).width + 2 * ctx.lineWidth; 1044 | canvas.height = 4 * size; 1045 | ctx.font = `${size}px Ubuntu`; 1046 | ctx.lineWidth = settings.showTextOutline ? Math.max(~~(size / 10), 2) : 2; 1047 | ctx.textBaseline = "middle"; 1048 | ctx.textAlign = "center"; 1049 | ctx.fillStyle = "#FFF" 1050 | ctx.strokeStyle = "#000"; 1051 | ctx.translate(canvas.width / 2, 2 * size); 1052 | (ctx.lineWidth !== 1) && ctx.strokeText(text, 0, 0); 1053 | ctx.fillText(text, 0, 0); 1054 | } 1055 | function drawRaw(ctx, x, y, text, size) { 1056 | ctx.font = `${size}px Ubuntu`; 1057 | ctx.textBaseline = "middle"; 1058 | ctx.textAlign = "center"; 1059 | ctx.lineWidth = settings.showTextOutline ? Math.max(~~(size / 10), 2) : 2; 1060 | ctx.fillStyle = "#FFF" 1061 | ctx.strokeStyle = "#000"; 1062 | (ctx.lineWidth !== 1) && ctx.strokeText(text, x, y); 1063 | ctx.fillText(text, x, y); 1064 | ctx.restore(); 1065 | } 1066 | function newNameCache(value, size) { 1067 | var canvas = document.createElement("canvas"); 1068 | var ctx = canvas.getContext("2d"); 1069 | drawTextOnto(canvas, ctx, value, size); 1070 | 1071 | cachedNames[value] = cachedNames[value] || { }; 1072 | cachedNames[value][size] = { 1073 | width: canvas.width, 1074 | height: canvas.height, 1075 | canvas: canvas, 1076 | value: value, 1077 | size: size, 1078 | accessTime: syncAppStamp 1079 | }; 1080 | return cachedNames[value][size]; 1081 | } 1082 | function newMassCache(size) { 1083 | var canvases = { 1084 | "0": { }, "1": { }, "2": { }, "3": { }, "4": { }, 1085 | "5": { }, "6": { }, "7": { }, "8": { }, "9": { } 1086 | }; 1087 | for (var value in canvases) { 1088 | var canvas = canvases[value].canvas = document.createElement("canvas"); 1089 | var ctx = canvas.getContext("2d"); 1090 | drawTextOnto(canvas, ctx, value, size); 1091 | canvases[value].canvas = canvas; 1092 | canvases[value].width = canvas.width; 1093 | canvases[value].height = canvas.height; 1094 | } 1095 | cachedMass[size] = { 1096 | canvases: canvases, 1097 | size: size, 1098 | lineWidth: settings.showTextOutline ? Math.max(~~(size / 10), 2) : 2, 1099 | accessTime: syncAppStamp 1100 | }; 1101 | return cachedMass[size]; 1102 | } 1103 | function toleranceTest(a, b, tolerance) { 1104 | return (a - tolerance) <= b && b <= (a + tolerance); 1105 | } 1106 | function getNameCache(value, size) { 1107 | if (!cachedNames[value]) return newNameCache(value, size); 1108 | var sizes = Object.keys(cachedNames[value]); 1109 | for (var i = 0, l = sizes.length; i < l; i++) 1110 | if (toleranceTest(size, sizes[i], size / 4)) 1111 | return cachedNames[value][sizes[i]]; 1112 | return newNameCache(value, size); 1113 | } 1114 | function getMassCache(size) { 1115 | var sizes = Object.keys(cachedMass); 1116 | for (var i = 0, l = sizes.length; i < l; i++) 1117 | if (toleranceTest(size, sizes[i], size / 4)) 1118 | return cachedMass[sizes[i]]; 1119 | return newMassCache(size); 1120 | } 1121 | 1122 | function drawText(ctx, isMass, x, y, size, drawSize, value) { 1123 | ctx.save(); 1124 | if (size > 500) return drawRaw(ctx, x, y, value, drawSize); 1125 | ctx.imageSmoothingQuality = "high"; 1126 | if (isMass) { 1127 | var cache = getMassCache(size); 1128 | cache.accessTime = syncAppStamp; 1129 | var canvases = cache.canvases; 1130 | var correctionScale = drawSize / cache.size; 1131 | 1132 | // calculate width 1133 | var width = 0; 1134 | for (var i = 0; i < value.length; i++) 1135 | width += canvases[value[i]].width - 2 * cache.lineWidth; 1136 | 1137 | ctx.scale(correctionScale, correctionScale); 1138 | x /= correctionScale; 1139 | y /= correctionScale; 1140 | x -= width / 2; 1141 | for (var i = 0; i < value.length; i++) { 1142 | var item = canvases[value[i]]; 1143 | ctx.drawImage(item.canvas, x, y - item.height / 2); 1144 | x += item.width - 2 * cache.lineWidth; 1145 | } 1146 | } else { 1147 | var cache = getNameCache(value, size); 1148 | cache.accessTime = syncAppStamp; 1149 | var canvas = cache.canvas; 1150 | var correctionScale = drawSize / cache.size; 1151 | ctx.scale(correctionScale, correctionScale); 1152 | x /= correctionScale; 1153 | y /= correctionScale; 1154 | ctx.drawImage(canvas, x - canvas.width / 2, y - canvas.height / 2); 1155 | } 1156 | ctx.restore(); 1157 | } 1158 | 1159 | function init() { 1160 | mainCanvas = document.getElementById("canvas"); 1161 | mainCtx = mainCanvas.getContext("2d"); 1162 | chatBox = document.getElementById("chat_textbox"); 1163 | mainCanvas.focus(); 1164 | function handleScroll(event) { 1165 | mouseZ *= Math.pow(.9, event.wheelDelta / -120 || event.detail || 0); 1166 | 1 > mouseZ && (mouseZ = 1); 1167 | mouseZ > 4 / mouseZ && (mouseZ = 4 / mouseZ); 1168 | } 1169 | if (/firefox/i.test(navigator.userAgent)) 1170 | document.addEventListener("DOMMouseScroll", handleScroll, false); 1171 | else 1172 | document.body.onmousewheel = handleScroll; 1173 | wHandle.onkeydown = function(event) { 1174 | switch (event.keyCode) { 1175 | case 13: // enter 1176 | if (escOverlayShown) break; 1177 | if (!settings.showChat) break; 1178 | if (isTyping) { 1179 | chatBox.blur(); 1180 | var chattxt = chatBox.value; 1181 | if (chattxt.length > 0) sendChat(chattxt); 1182 | chatBox.value = ""; 1183 | } else chatBox.focus(); 1184 | break; 1185 | case 32: // space 1186 | if (isTyping || escOverlayShown || pressed.space) break; 1187 | wsSend(UINT8_CACHE[17]); 1188 | pressed.space = true; 1189 | break; 1190 | case 87: // W 1191 | if (isTyping || escOverlayShown) break; 1192 | wsSend(UINT8_CACHE[21]); 1193 | pressed.w = true; 1194 | break; 1195 | case 81: // Q 1196 | if (isTyping || escOverlayShown || pressed.q) break; 1197 | wsSend(UINT8_CACHE[18]); 1198 | pressed.q = true; 1199 | break; 1200 | case 69: // E 1201 | if (isTyping || escOverlayShown || pressed.e) break; 1202 | wsSend(UINT8_CACHE[22]); 1203 | pressed.e = true; 1204 | break; 1205 | case 82: // R 1206 | if (isTyping || escOverlayShown || pressed.r) break; 1207 | wsSend(UINT8_CACHE[23]); 1208 | pressed.r = true; 1209 | break; 1210 | case 84: // T 1211 | if (isTyping || escOverlayShown || pressed.t) break; 1212 | wsSend(UINT8_CACHE[24]); 1213 | pressed.t = true; 1214 | break; 1215 | case 80: // P 1216 | if (isTyping || escOverlayShown || pressed.p) break; 1217 | wsSend(UINT8_CACHE[25]); 1218 | pressed.p = true; 1219 | break; 1220 | case 27: // esc 1221 | if (pressed.esc) break; 1222 | pressed.esc = true; 1223 | if (escOverlayShown) hideESCOverlay(); 1224 | else showESCOverlay(); 1225 | break; 1226 | } 1227 | }; 1228 | wHandle.onkeyup = function(event) { 1229 | switch (event.keyCode) { 1230 | case 32: // space 1231 | pressed.space = false; 1232 | break; 1233 | case 87: // W 1234 | pressed.w = false; 1235 | break; 1236 | case 81: // Q 1237 | if (pressed.q) wsSend(UINT8_CACHE[19]); 1238 | pressed.q = false; 1239 | break; 1240 | case 69: // E 1241 | pressed.e = false; 1242 | break; 1243 | case 82: // R 1244 | pressed.r = false; 1245 | break; 1246 | case 84: // T 1247 | pressed.t = false; 1248 | break; 1249 | case 80: // P 1250 | pressed.p = false; 1251 | break; 1252 | case 27: // esc 1253 | pressed.esc = false; 1254 | break; 1255 | } 1256 | }; 1257 | chatBox.onblur = function() { 1258 | isTyping = false; 1259 | drawChat(); 1260 | }; 1261 | chatBox.onfocus = function() { 1262 | isTyping = true; 1263 | drawChat(); 1264 | }; 1265 | mainCanvas.onmousemove = function(event) { 1266 | mouseX = event.clientX; 1267 | mouseY = event.clientY; 1268 | }; 1269 | setInterval(function() { 1270 | // send mouse update 1271 | sendMouseMove( 1272 | (mouseX - mainCanvas.width / 2) / cameraZ + cameraX, 1273 | (mouseY - mainCanvas.height / 2) / cameraZ + cameraY 1274 | ); 1275 | }, 40); 1276 | wHandle.onresize = function() { 1277 | var cW = mainCanvas.width = wHandle.innerWidth, 1278 | cH = mainCanvas.height = wHandle.innerHeight; 1279 | viewMult = Math.sqrt(Math.min(cH / 1080, cW / 1920)); 1280 | }; 1281 | wHandle.onresize(); 1282 | log.info(`init done in ${Date.now() - LOAD_START}ms`); 1283 | gameReset(); 1284 | showESCOverlay(); 1285 | 1286 | if (settings.allowGETipSet && wHandle.location.search) { 1287 | var div = /ip=([\w\W]+):([0-9]+)/.exec(wHandle.location.search.slice(1)) 1288 | if (div) wsInit(`${div[1]}:${div[2]}`); 1289 | } 1290 | 1291 | window.requestAnimationFrame(drawGame); 1292 | } 1293 | wHandle.setserver = function(arg) { 1294 | if (wsUrl === arg) return; 1295 | wsInit(arg); 1296 | }; 1297 | wHandle.setDarkTheme = function(a) { 1298 | settings.darkTheme = a; 1299 | drawStats(); 1300 | }; 1301 | wHandle.setShowMass = function(a) { 1302 | settings.showMass = a; 1303 | }; 1304 | wHandle.setSkins = function(a) { 1305 | settings.showSkins = a; 1306 | }; 1307 | wHandle.setColors = function(a) { 1308 | settings.showColor = !a; 1309 | }; 1310 | wHandle.setNames = function(a) { 1311 | settings.showNames = a; 1312 | drawLeaderboard(); 1313 | }; 1314 | wHandle.setChatHide = function(a) { 1315 | settings.showChat = !a; 1316 | drawChat(); 1317 | }; 1318 | wHandle.setMinimap = function(a) { 1319 | settings.showMinimap = !a; 1320 | }; 1321 | wHandle.spectate = function(a) { 1322 | wsSend(UINT8_CACHE[1]); 1323 | stats.maxScore = 0; 1324 | hideESCOverlay(); 1325 | }; 1326 | wHandle.play = function(a) { 1327 | sendPlay(a); 1328 | hideESCOverlay(); 1329 | }; 1330 | wHandle.openSkinsList = function() { 1331 | if (wjQuery("#inPageModalTitle").text() === "Skins") return; 1332 | wjQuery.get("include/gallery.php").then(function(data) { 1333 | wjQuery("#inPageModalTitle").text("Skins"); 1334 | wjQuery("#inPageModalBody").html(data); 1335 | }); 1336 | }; 1337 | wHandle.onload = init; 1338 | })(window, window.jQuery); 1339 | -------------------------------------------------------------------------------- /www/checkdir.php: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /www/include/gallery.php: -------------------------------------------------------------------------------- 1 | 2 |
If you cannot connect to the servers, check if you have some anti virus or firewall blocking the connection.
119 |