├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── about.html ├── audio ├── superhot.mp3 └── superhot.ogg ├── cache.appcache ├── css └── supercold.css ├── favicon.ico ├── favicon.png ├── img ├── README.md └── wallpaper.png ├── index.html ├── js ├── ads.js ├── dataview-polyfill.js ├── phaser-supercold.js ├── phaser-supercold.min.js └── supercold.js ├── privacy.html └── terms.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS files must always use LF for tools to work 5 | *.js eol=lf 6 | 7 | *.html eol=lf 8 | *.css eol=lf 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Vasilis Poulimenos 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of SUPERCOLD nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SUPERCOLD 2 | ========= 3 | 4 | A simple 2D HTML5 game with the clever mechanics of [SUPERHOT](http://superhotgame.com/). 5 | 6 | [You can play it here!](https://import-this.github.io/supercold/) 7 | 8 | Made with [Phaser](http://phaser.io/). 9 | 10 | ## License 11 | SUPERCOLD is released under the [BSD 3-Clause License](https://github.com/import-this/supercold/blob/master/LICENSE). 12 | -------------------------------------------------------------------------------- /about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | About SUPERCOLD 27 | 28 | 29 | 38 | 39 | 40 |

Version 1.3.0

41 |

42 | Created by import-this.
43 | Check out some more of my games.
44 | Follow me on GitHub. 45 |

46 |

CHANGELOG

47 |

48 | 49 |

50 | 51 | 52 | -------------------------------------------------------------------------------- /audio/superhot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/import-this/supercold/bf1861dbea8ad2818df76ddbb6397fcd608b3c25/audio/superhot.mp3 -------------------------------------------------------------------------------- /audio/superhot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/import-this/supercold/bf1861dbea8ad2818df76ddbb6397fcd608b3c25/audio/superhot.ogg -------------------------------------------------------------------------------- /cache.appcache: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # Update the next line after a change is made. 3 | # 2017-05-11:v1.3.0 4 | 5 | # Do NOT use backslashes before relative paths. 6 | 7 | # HTML 8 | index.html 9 | about.html 10 | terms.html 11 | privacy.html 12 | 13 | # CSS 14 | css/supercold.css 15 | 16 | # JavaScript 17 | js/dataview-polyfill.js 18 | js/phaser-supercold.min.js 19 | js/supercold.js 20 | 21 | # Images 22 | favicon.ico 23 | favicon.png 24 | 25 | # Audio 26 | audio/superhot.mp3 27 | audio/superhot.ogg 28 | 29 | # Resources that require the user to be online (everything not listed above). 30 | NETWORK: 31 | * 32 | -------------------------------------------------------------------------------- /css/supercold.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | /* https://www.paulirish.com/2012/box-sizing-border-box-ftw/ */ 7 | html { 8 | -webkit-box-sizing: border-box; 9 | -moz-box-sizing: border-box; 10 | box-sizing: border-box; 11 | } 12 | *, *:before, *:after { 13 | -webkit-box-sizing: inherit; 14 | -moz-box-sizing: inherit; 15 | box-sizing: inherit; 16 | } 17 | 18 | /* Responsive typography. Ugh! */ 19 | body { 20 | font-size: 14px; 21 | } 22 | @media (min-width: 768px) { 23 | body { 24 | font-size: 16px; 25 | } 26 | } 27 | 28 | /* Fancy checkboxes */ 29 | /* http://stackoverflow.com/questions/4148499/how-to-style-checkbox-using-css */ 30 | input[type='checkbox'] { 31 | display: none; 32 | } 33 | input[type='checkbox'] + label { 34 | position: relative; 35 | cursor: pointer; 36 | } 37 | input[type='checkbox'] + label > span { 38 | -webkit-box-sizing: content-box; 39 | -moz-box-sizing: content-box; 40 | box-sizing: content-box; 41 | 42 | display: inline-block; 43 | /* The text is not aligned exactly with the checkbox. */ 44 | vertical-align: -3px; 45 | width: 15px; 46 | height: 15px; 47 | margin-right: 4px; 48 | color: rgb(245, 4, 3); 49 | text-shadow: -1px 1px 6px rgb(245, 4, 3), 50 | 1px -1px 6px rgb(245, 4, 3); 51 | background-color: #525252; 52 | text-align: center; 53 | cursor: pointer; 54 | 55 | border: 4px solid #3a3a3a; 56 | border-radius: 3px; 57 | } 58 | input[type='checkbox'] + label > span::before { 59 | content: '\2714'; /* Tick sign. */ 60 | opacity: 0; 61 | /* The tick sign is not placed exactly in the center. */ 62 | position: relative; 63 | top: -3px; 64 | } 65 | input[type='checkbox'] + label:hover > span::before { 66 | opacity: 0.6; 67 | } 68 | input[type='checkbox']:checked + label > span::before { 69 | opacity: 1; 70 | } 71 | /* Disabled checkboxes need something extra. */ 72 | input[type='checkbox']:disabled + label, input[type='checkbox']:disabled + label > span { 73 | opacity: 0.6; 74 | cursor: default; 75 | } 76 | input[type='checkbox']:disabled + label:hover > span::before { 77 | opacity: 0; 78 | } 79 | 80 | /* Fancy radio buttons */ 81 | input[type='radio'] { 82 | display: none; 83 | } 84 | input[type='radio'] + label { 85 | position: relative; 86 | cursor: pointer; 87 | } 88 | input[type='radio'] + label > span { 89 | -webkit-box-sizing: content-box; 90 | -moz-box-sizing: content-box; 91 | box-sizing: content-box; 92 | 93 | display: inline-block; 94 | /* The text is not aligned exactly with the radio button. */ 95 | vertical-align: -6px; 96 | width: 15px; 97 | height: 15px; 98 | margin-right: 3px; 99 | color: rgb(245, 4, 3); 100 | text-shadow: -1px 1px 6px rgb(245, 4, 3), 101 | 1px -1px 6px rgb(245, 4, 3); 102 | background-color: #525252; 103 | text-align: center; 104 | cursor: pointer; 105 | 106 | border: 4px solid #3a3a3a; 107 | border-radius: 100%; 108 | } 109 | input[type='radio'] + label:hover > span { 110 | background-color: rgba(245, 4, 3, 0.6); 111 | } 112 | input[type='radio']:checked + label > span { 113 | background-color: rgba(245, 4, 3, 0.85); 114 | } 115 | /* Disabled radio buttons need something extra. */ 116 | input[type='radio']:disabled + label, input[type='radio']:disabled + label > span { 117 | opacity: 0.6; 118 | cursor: default; 119 | } 120 | input[type='radio']:disabled + label:hover > span { 121 | background-color: #525252; 122 | } 123 | 124 | /* http://stackoverflow.com/a/4407335/1751037 */ 125 | .no-select { 126 | -webkit-touch-callout: none; /* iOS Safari */ 127 | -webkit-user-select: none; /* Chrome/Safari/Opera */ 128 | -khtml-user-select: none; /* Konqueror */ 129 | -moz-user-select: none; /* Firefox */ 130 | -ms-user-select: none; /* Internet Explorer/Edge */ 131 | user-select: none; 132 | cursor: default; 133 | } 134 | .no-touch-action { 135 | -ms-touch-action: none; 136 | touch-action: none; 137 | } 138 | 139 | html, body, #supercold-canvas { 140 | width: 100%; 141 | height: 100%; 142 | overflow: hidden; /* no scrollbar */ 143 | } 144 | body { 145 | font-family: Helvetica, Arial; 146 | color: rgb(230, 251, 255); 147 | text-shadow: 0px -1px 8px rgba(230, 251, 255, 1), 148 | 0px 1px 8px rgba(230, 251, 255, 1), 149 | -1px 0px 4px rgba(245, 4, 3, 0.275), 150 | 1px 0px 4px rgba(220, 250, 255, 1); 151 | background-color: rgb(10, 10, 10); 152 | } 153 | a { 154 | color: rgb(235, 251, 255); 155 | text-decoration: none; 156 | } 157 | button { 158 | margin: 4px 0; 159 | padding: 3px 40px; 160 | 161 | font-size: 1.15em; 162 | color: rgb(235, 251, 255); 163 | text-shadow: -1px 0 4px rgb(230, 251, 255), 164 | 1px 0 4px rgb(230, 251, 255); 165 | 166 | background-color: rgb(34, 34, 34); 167 | background: -webkit-linear-gradient(top, rgb(70, 70, 70), rgb(34, 34, 34)); 168 | background: -moz-linear-gradient(top, rgb(70, 70, 70), rgb(34, 34, 34)); 169 | background: -ms-linear-gradient(top, rgb(70, 70, 70), rgb(34, 34, 34)); 170 | background: -o-linear-gradient(top, rgb(70, 70, 70), rgb(34, 34, 34)); 171 | background: linear-gradient(to bottom, rgb(70, 70, 70), rgb(34, 34, 34)); 172 | 173 | border: 2px solid rgb(34, 34, 34); 174 | border-radius: 6px; 175 | 176 | -webkit-box-shadow: 0 0 5px 1px rgb(70, 70, 70); 177 | box-shadow: 0 0 5px 1px rgb(70, 70, 70); 178 | 179 | -webkit-transition-duration: 0.2s; 180 | -moz-transition-duration: 0.2s; 181 | -o-transition-duration: 0.2s; 182 | transition-duration: 0.2s; 183 | } 184 | button:hover { 185 | color: rgb(34, 34, 34); 186 | text-shadow: -1px 0 2px rgb(34, 34, 34), 187 | 1px 0 2px rgb(34, 34, 34); 188 | 189 | background-color: rgb(245, 4, 3); 190 | background: -webkit-linear-gradient(top, rgb(245, 30, 30), rgb(245, 4, 3)); 191 | background: -moz-linear-gradient(top, rgb(245, 30, 30), rgb(245, 4, 3)); 192 | background: -ms-linear-gradient(top, rgb(245, 30, 30), rgb(245, 4, 3)); 193 | background: -o-linear-gradient(top, rgb(245, 30, 30), rgb(245, 4, 3)); 194 | background: linear-gradient(to bottom, rgb(245, 30, 30), rgb(245, 4, 3)); 195 | 196 | border: 2px solid #f44336; 197 | 198 | -webkit-box-shadow: 0 0 5px 1px rgb(245, 4, 3); 199 | box-shadow: 0 0 5px 1px rgb(245, 4, 3); 200 | } 201 | 202 | #center { 203 | position: fixed; 204 | left: 50%; 205 | top: 50%; 206 | -webkit-transform: translate(-50%, -50%); 207 | -moz-transform: translate(-50%, -50%); 208 | -ms-transform: translate(-50%, -50%); 209 | -o-transform: translate(-50%, -50%); 210 | transform: translate(-50%, -50%); 211 | 212 | /* Prevent wrapping on smaller screens! */ 213 | min-width: 655px; 214 | text-align: center; 215 | padding: 0.675em 1em; 216 | border-radius: 8px; 217 | 218 | background-color: rgba(40, 40, 40, 0.925); 219 | background: -webkit-radial-gradient(rgba(40, 40, 40, 0.925), rgba(16, 16, 16, 0.925)); 220 | background: -moz-radial-gradient(rgba(40, 40, 40, 0.925), rgba(16, 16, 16, 0.925)); 221 | background: -ms-radial-gradient(rgba(40, 40, 40, 0.925), rgba(16, 16, 16, 0.925)); 222 | background: -o-radial-gradient(rgba(40, 40, 40, 0.925), rgba(16, 16, 16, 0.925)); 223 | background: radial-gradient(rgba(40, 40, 40, 0.925), rgba(16, 16, 16, 0.925)); 224 | 225 | -webkit-box-shadow: 0 0 6px 1px rgb(24, 24, 24); 226 | box-shadow: 0 0 6px 1px rgb(24, 24, 24); 227 | } 228 | @media (min-width: 768px) { 229 | #center { 230 | /* Prevent wrapping on smaller screens! */ 231 | min-width: 728px; 232 | } 233 | } 234 | #panes { 235 | display: table; 236 | margin: 0 auto; 237 | border-spacing: 0.75em; 238 | } 239 | .pane { 240 | display: table-cell; 241 | vertical-align: middle; 242 | } 243 | .pane div { 244 | margin: 4px 0; 245 | } 246 | 247 | #supercold { 248 | font-size: 1.75em; 249 | font-weight: lighter; 250 | 251 | color: rgb(235, 251, 255); 252 | text-shadow: 0px -1px 6px rgba(220, 251, 255, 1), 253 | 0px 1px 6px rgba(220, 251, 255, 1), 254 | -1px 0px 4px rgba(245, 4, 3, 0.225), 255 | 1px 0px 4px rgba(220, 250, 255, 1); 256 | } 257 | #supercold span { 258 | text-shadow: 0px 0px 6px rgba(220, 251, 255, 1), 259 | 0px -1px 6px rgba(220, 251, 255, 1), 260 | 0px 1px 6px rgba(220, 251, 255, 1), 261 | -1px 0px 4px rgba(245, 4, 3, 0.725), 262 | 1px 0px 4px rgba(220, 251, 255, 1); 263 | } 264 | #superhot { 265 | cursor: pointer; 266 | font-size: 1.4em; 267 | border-bottom: 1px dotted rgba(245, 4, 3, 0.5); 268 | color: rgb(245, 4, 3); 269 | text-shadow: -1px 1px 6px rgb(245, 4, 3), 270 | 1px -1px 6px rgb(245, 4, 3); 271 | } 272 | #superhot span { 273 | text-shadow: 0px 0px 4px rgb(245, 4, 3), 274 | -1px 1px 4px rgb(245, 4, 3), 275 | 1px -1px 4px rgb(245, 4, 3); 276 | } 277 | #supercold, #superhot { 278 | font-weight: normal; 279 | } 280 | #supercold span, #superhot span { 281 | font-weight: bold; 282 | } 283 | 284 | #description strong { 285 | display: block; 286 | } 287 | #instructions { 288 | line-height: 1.25; 289 | } 290 | #instructions .superhotkey { 291 | color: rgb(245, 4, 3); 292 | text-shadow: 0px 0px 3px rgb(245, 4, 3), 293 | -1px 1px 5px rgb(245, 4, 3), 294 | 1px -1px 5px rgb(245, 4, 3); 295 | } 296 | #level { 297 | color: rgb(230, 251, 255); 298 | background-color: #3a3a3a; 299 | text-align: center; 300 | border: 2px solid rgb(210, 231, 235); 301 | text-shadow: -1px 0 4px rgb(230, 251, 255), 302 | 1px 0 4px rgb(230, 251, 255); 303 | -webkit-box-shadow: 0 0 8px 1px rgb(210, 231, 235); 304 | box-shadow: 0 0 8px 1px rgb(210, 231, 235); 305 | } 306 | #start { 307 | cursor: pointer; 308 | margin-top: 0.4em; 309 | } 310 | 311 | #mutator-pane { 312 | font-size: 0.9em; 313 | } 314 | #mutator-pane div { 315 | margin: 2px 0; 316 | } 317 | #mutators { 318 | display: inline-block; 319 | text-align: left; 320 | } 321 | #guns > div { 322 | display: inline-block; 323 | padding: 0 2px; 324 | } 325 | .locked10, .locked20, .locked30, .locked40, .locked50, .locked60, .locked70, 326 | .locked75, .locked80, .locked90, .locked100, .locked120, .locked128 { 327 | display: none; 328 | } 329 | 330 | #more { 331 | position: fixed; 332 | top: 0; 333 | left: 0; 334 | padding: 4px 8px; 335 | 336 | background-color: rgba(40, 40, 40, 0.75); 337 | border-bottom-right-radius: 6px; 338 | 339 | -webkit-box-shadow: 0 0 6px 1px rgb(128, 128, 128); 340 | box-shadow: 0 0 6px 1px rgb(128, 128, 128); 341 | } 342 | #bottom { 343 | position: fixed; 344 | bottom: 0; 345 | right: 0; 346 | padding: 4px 8px; 347 | 348 | font-size: 0.96em; 349 | background-color: rgba(40, 40, 40, 0.75); 350 | border-top-left-radius: 6px; 351 | 352 | -webkit-box-shadow: 0 0 5px 1px rgb(128, 128, 128); 353 | box-shadow: 0 0 5px 1px rgb(128, 128, 128); 354 | } 355 | #top { 356 | position: fixed; 357 | top: 4px; 358 | right: 4px; 359 | } 360 | #top a { 361 | /* Hide text of FB share and Twitter buttons until the plugins have loaded. */ 362 | color: rgb(10, 10, 10); 363 | text-shadow: none; 364 | } 365 | #top > div { 366 | vertical-align: top; 367 | /* Override default FB display style for mobile. */ 368 | display: inline-block; 369 | } 370 | #top > iframe { 371 | /* Use this instead of frameborder=0. */ 372 | border: none; 373 | } 374 | 375 | #tips, #hint { 376 | display: none; 377 | pointer-events: none; 378 | position: fixed; 379 | left: 0; 380 | width: 100%; 381 | text-align: center; 382 | font-size: 1.5em; 383 | color: rgb(242, 251, 255); 384 | text-shadow: 0 -1px 7px rgba(242, 251, 255, 0.975), 385 | 0 1px 7px rgba(242, 251, 255, 0.975); 386 | } 387 | #tips { 388 | top: 13%; 389 | } 390 | #hint { 391 | bottom: 13%; 392 | } 393 | @media (min-width: 550px) { 394 | #tips, #hint { 395 | font-size: 2em; 396 | } 397 | } 398 | @media (min-width: 768px) { 399 | #tips, #hint { 400 | font-size: 2.5em; 401 | } 402 | } 403 | .tip { 404 | display: none; 405 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/import-this/supercold/bf1861dbea8ad2818df76ddbb6397fcd608b3c25/favicon.ico -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/import-this/supercold/bf1861dbea8ad2818df76ddbb6397fcd608b3c25/favicon.png -------------------------------------------------------------------------------- /img/README.md: -------------------------------------------------------------------------------- 1 | Image for Facebook crawler. 2 | -------------------------------------------------------------------------------- /img/wallpaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/import-this/supercold/bf1861dbea8ad2818df76ddbb6397fcd608b3c25/img/wallpaper.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | SUPERCOLD 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 51 | 52 | 53 | 54 |
55 | 64 | 65 |
66 | 67 |
68 | 250 |
251 |
252 | Don't rush it! 253 |
254 |
255 | You can destroy enemy bullets with your bullets! 256 |
257 |
258 | Bots can kill each other if you make them get in each others way. 259 |
260 |
261 | Sometimes bots try to dodge your bullets. 262 |
263 |
264 | Time moves slightly faster when you look around. 265 |
266 |
267 | Be careful not to trap yourself in the corners. 268 |
269 |
270 | Try to stay in the center. 271 |
272 |
273 | Try out some of the several mutators. 274 |
275 |
276 | Reach level 128 for a really fun mutator! 277 |
278 |
279 | If you are playing with limited bullets and you run out,
280 | your only option is to make the bots kill each other. 281 |
282 |
283 | Hotswitching is powerful. Use it intelligently. 284 |
285 |
286 | You can also fire by pressing the spacebar. 287 |
288 |
289 | You can also hotswitch by right clicking on a bot,
290 | but it may not work correctly in your browser.
291 | Try at your own risk. 292 |
293 |
294 |
295 | press 'T' to restart instantly 296 |
297 | 298 | 299 | 324 | 325 | 326 | 327 | 337 | 338 | 339 | -------------------------------------------------------------------------------- /js/ads.js: -------------------------------------------------------------------------------- 1 | window.noAdblock = true; 2 | -------------------------------------------------------------------------------- /js/dataview-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * By Benvie https://gist.github.com/Benvie/5020656 3 | */ 4 | 5 | void function(global){ 6 | if ('DataView' in global && 'ArrayBuffer' in global) { 7 | return; 8 | } 9 | 10 | var hide = (function(){ 11 | // check if we're in ES5 12 | if (typeof Object.getOwnPropertyNames === 'function' && !('prototype' in Object.getOwnPropertyNames)) { 13 | var hidden = { enumerable: false }; 14 | 15 | return function(object, key){ 16 | Object.defineProperty(object, key, hidden); 17 | }; 18 | } 19 | 20 | // noop for ES3 21 | return function(){}; 22 | })(); 23 | 24 | function define(object, props){ 25 | for (var key in props) { 26 | object[key] = props[key]; 27 | } 28 | } 29 | 30 | 31 | var ArrayBuffer = global.ArrayBuffer = (function(){ 32 | var min = Math.min, 33 | max = Math.max, 34 | char = String.fromCharCode; 35 | 36 | var chars = {}, 37 | indices = []; 38 | 39 | // create cached mapping of characters to char codes and back 40 | void function(){ 41 | for (var i = 0; i < 0x100; ++i) { 42 | chars[indices[i] = char(i)] = i; 43 | if (i >= 0x80) { 44 | chars[char(0xf700 + i)] = i; 45 | } 46 | } 47 | }(); 48 | 49 | // read a string into an array of bytes 50 | function readString(string){ 51 | var array = [], 52 | cycles = string.length % 8, 53 | index = 0; 54 | 55 | while (cycles--) { 56 | array[index] = chars[string[index++]]; 57 | } 58 | 59 | cycles = string.length >> 3; 60 | 61 | while (cycles--) { 62 | array.push( 63 | chars[string[index]], 64 | chars[string[index+1]], 65 | chars[string[index+2]], 66 | chars[string[index+3]], 67 | chars[string[index+4]], 68 | chars[string[index+5]], 69 | chars[string[index+6]], 70 | chars[string[index+7]] 71 | ); 72 | index += 8; 73 | } 74 | 75 | return array; 76 | } 77 | 78 | // write an array of bytes to a string 79 | function writeString(array){ 80 | try { return char.apply(null, array) } catch (e) {} 81 | 82 | var string = '', 83 | cycles = array.length % 8, 84 | index = 0; 85 | 86 | while (cycles--) { 87 | string += indices[array[index++]]; 88 | } 89 | 90 | cycles = array.length >> 3; 91 | 92 | while (cycles--) { 93 | string += 94 | indices[array[index]] + 95 | indices[array[index+1]] + 96 | indices[array[index+2]] + 97 | indices[array[index+3]] + 98 | indices[array[index+4]] + 99 | indices[array[index+5]] + 100 | indices[array[index+6]] + 101 | indices[array[index+7]]; 102 | index += 8; 103 | } 104 | 105 | return string; 106 | } 107 | 108 | // create a new array of given size where each element is 0 109 | function zerodArray(size){ 110 | var data = new Array(size); 111 | 112 | for (var i=0; i < size; i++) { 113 | data[i] = 0; 114 | } 115 | 116 | return data; 117 | } 118 | 119 | 120 | // ################### 121 | // ### ArrayBuffer ### 122 | // ################### 123 | 124 | function ArrayBuffer(length){ 125 | if (length instanceof ArrayBuffer) { 126 | this._data = length._data.slice(); 127 | } else if (typeof length === 'string') { 128 | this._data = readString(length); 129 | } else { 130 | if ((length >>= 0) < 0) { 131 | throw new RangeError('ArrayBuffer length must be non-negative'); 132 | } 133 | this._data = zerodArray(length); 134 | } 135 | 136 | this.byteLength = this._data.length; 137 | hide(this, '_data'); 138 | } 139 | 140 | define(ArrayBuffer, { 141 | toByteString: function toByteString(arraybuffer){ 142 | if (!(arraybuffer instanceof ArrayBuffer)) { 143 | throw new TypeError('ArrayBuffer.toByteString requires an ArrayBuffer'); 144 | } 145 | 146 | return writeString(arraybuffer._data); 147 | } 148 | }); 149 | 150 | define(ArrayBuffer.prototype, { 151 | slice: function slice(begin, end){ 152 | var arraybuffer = new ArrayBuffer(0); 153 | 154 | arraybuffer._data = this._data.slice(begin, end); 155 | arraybuffer.byteLength = arraybuffer._data.length; 156 | 157 | return arraybuffer; 158 | } 159 | }); 160 | 161 | return ArrayBuffer; 162 | })(); 163 | 164 | 165 | 166 | global.DataView = (function(){ 167 | var log = Math.log, 168 | pow = Math.pow, 169 | LN2 = Math.LN2; 170 | 171 | 172 | // Joyent copyright applies to readFloat and writeFloat 173 | 174 | // Copyright Joyent, Inc. and other Node contributors. 175 | // 176 | // Permission is hereby granted, free of charge, to any person obtaining a 177 | // copy of this software and associated documentation files (the 178 | // "Software"), to deal in the Software without restriction, including 179 | // without limitation the rights to use, copy, modify, merge, publish, 180 | // distribute, sublicense, and/or sell copies of the Software, and to permit 181 | // persons to whom the Software is furnished to do so, subject to the 182 | // following conditions: 183 | // 184 | // The above copyright notice and this permission notice shall be included 185 | // in all copies or substantial portions of the Software. 186 | // 187 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 188 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 189 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 190 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 191 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 192 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 193 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 194 | 195 | function readFloat(dataview, offset, littleEndian, mLen, bytes){ 196 | var buffer = dataview.buffer._data, 197 | offset = dataview.byteOffset + offset, 198 | e, m, 199 | eLen = bytes * 8 - mLen - 1, 200 | eMax = (1 << eLen) - 1, 201 | eBias = eMax >> 1, 202 | nBits = -7, 203 | i = littleEndian ? bytes - 1 : 0 , 204 | d = littleEndian ? -1 : 1, 205 | s = buffer[offset + i]; 206 | 207 | i += d; 208 | 209 | e = s & ((1 << (-nBits)) - 1); 210 | s >>= (-nBits); 211 | nBits += eLen; 212 | for (; nBits > 0; e = e * 0x100 + buffer[offset + i], i += d, nBits -= 8); 213 | 214 | m = e & ((1 << (-nBits)) - 1); 215 | e >>= (-nBits); 216 | nBits += mLen; 217 | for (; nBits > 0; m = m * 0x100 + buffer[offset + i], i += d, nBits -= 8); 218 | 219 | if (e === 0) { 220 | e = 1 - eBias; 221 | } else if (e === eMax) { 222 | return m ? NaN : s ? -Infinity : Infinity; 223 | } else { 224 | m = m + pow(2, mLen); 225 | e = e - eBias; 226 | } 227 | return (s ? -1 : 1) * m * pow(2, e - mLen); 228 | } 229 | 230 | function writeFloat(dataview, offset, value, littleEndian, mLen, bytes){ 231 | var buffer = dataview.buffer._data, 232 | offset = dataview.byteOffset + offset, 233 | e, m, c, 234 | eLen = bytes * 8 - mLen - 1, 235 | eMax = (1 << eLen) - 1, 236 | eBias = eMax >> 1, 237 | rt = (mLen === 23 ? pow(2, -24) - pow(2, -77) : 0), 238 | i = littleEndian ? 0 : bytes - 1, 239 | d = littleEndian ? 1 : -1, 240 | s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; 241 | 242 | value < 0 && (value = -value); 243 | 244 | if (value !== value || value === Infinity) { 245 | m = value !== value ? 1 : 0; 246 | e = eMax; 247 | } else { 248 | e = (log(value) / LN2) | 0; 249 | if (value * (c = pow(2, -e)) < 1) { 250 | e--; 251 | c *= 2; 252 | } 253 | if (e + eBias >= 1) { 254 | value += rt / c; 255 | } else { 256 | value += rt * pow(2, 1 - eBias); 257 | } 258 | if (value * c >= 2) { 259 | e++; 260 | c /= 2; 261 | } 262 | 263 | if (e + eBias >= eMax) { 264 | m = 0; 265 | e = eMax; 266 | } else if (e + eBias >= 1) { 267 | m = (value * c - 1) * pow(2, mLen); 268 | e = e + eBias; 269 | } else { 270 | m = value * pow(2, eBias - 1) * pow(2, mLen); 271 | e = 0; 272 | } 273 | } 274 | 275 | for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 0x100, mLen -= 8); 276 | 277 | e = (e << mLen) | m; 278 | eLen += mLen; 279 | for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 0x100, eLen -= 8); 280 | 281 | buffer[offset + i - d] |= s * 0x80; 282 | } 283 | 284 | 285 | var le2 = [1, 0], 286 | le4 = [3, 2, 1, 0], 287 | be2 = [0, 1], 288 | be4 = [0, 1, 2, 3]; 289 | 290 | function readUint8(dataview, byteOffset){ 291 | var buffer = dataview.buffer._data, 292 | offset = byteOffset + dataview.byteOffset; 293 | 294 | return buffer[offset]; 295 | } 296 | 297 | function readUint16(dataview, byteOffset, littleEndian){ 298 | var buffer = dataview.buffer._data, 299 | offset = byteOffset + dataview.byteOffset, 300 | order = littleEndian ? le2 : be2; 301 | 302 | var b0 = buffer[offset + order[0]], 303 | b1 = buffer[offset + order[1]] << 8; 304 | 305 | return b0 | b1; 306 | } 307 | 308 | function readUint32(dataview, byteOffset, littleEndian){ 309 | var buffer = dataview.buffer._data, 310 | offset = byteOffset + dataview.byteOffset, 311 | order = littleEndian ? le4 : be4; 312 | 313 | var b0 = buffer[offset + order[0]], 314 | b1 = buffer[offset + order[1]] << 8, 315 | b2 = buffer[offset + order[2]] << 16, 316 | b3 = buffer[offset + order[3]] << 24; 317 | 318 | return b0 | b1 | b2 | b3; 319 | } 320 | 321 | 322 | function boundsCheck(offset, size, max){ 323 | if (offset < 0) { 324 | throw new RangeError('Tried to write to a negative index'); 325 | } else if (offset + size > max) { 326 | throw new RangeError('Tried to write '+size+' bytes past the end of a buffer at index '+offset+' of '+max); 327 | } 328 | } 329 | 330 | 331 | function writeUint8(dataview, byteOffset, value){ 332 | var buffer = dataview.buffer._data, 333 | offset = byteOffset + dataview.byteOffset; 334 | 335 | boundsCheck(offset, 1, buffer.length); 336 | 337 | buffer[offset] = value & 0xff; 338 | } 339 | 340 | function writeUint16(dataview, byteOffset, value, littleEndian){ 341 | var buffer = dataview.buffer._data, 342 | order = littleEndian ? le2 : be2, 343 | offset = byteOffset + dataview.byteOffset; 344 | 345 | boundsCheck(offset, 2, buffer.length); 346 | 347 | buffer[offset + order[0]] = value & 0xff; 348 | buffer[offset + order[1]] = value >>> 8 & 0xff; 349 | } 350 | 351 | function writeUint32(dataview, byteOffset, value, littleEndian){ 352 | var buffer = dataview.buffer._data, 353 | order = littleEndian ? le4 : be4, 354 | offset = byteOffset + dataview.byteOffset; 355 | 356 | boundsCheck(offset, 4, buffer.length); 357 | 358 | buffer[offset + order[0]] = value & 0xff; 359 | buffer[offset + order[1]] = value >>> 8 & 0xff; 360 | buffer[offset + order[2]] = value >>> 16 & 0xff; 361 | buffer[offset + order[3]] = value >>> 24 & 0xff; 362 | } 363 | 364 | 365 | 366 | // ################ 367 | // ### DataView ### 368 | // ################ 369 | 370 | function DataView(buffer, byteOffset, byteLength){ 371 | if (!(buffer instanceof ArrayBuffer)) { 372 | throw new TypeError('DataView must be initialized with an ArrayBuffer'); 373 | } 374 | 375 | if (byteOffset === undefined) { 376 | this.byteOffset = buffer.byteOffset >> 0; 377 | } else { 378 | this.byteOffset = byteOffset >> 0; 379 | } 380 | 381 | if (this.byteOffset < 0) { 382 | throw new RangeError('DataView byteOffset must be non-negative'); 383 | } 384 | 385 | 386 | if (byteLength === undefined) { 387 | this.byteLength = (buffer.byteLength - this.byteOffset) >> 0; 388 | } else { 389 | this.byteLength = byteLength >> 0; 390 | } 391 | 392 | if (this.byteLength < 0) { 393 | throw new RangeError('DataView byteLength must be non-negative'); 394 | } 395 | 396 | 397 | if (this.byteOffset + this.byteLength > buffer.byteLength) { 398 | throw new RangeError('DataView byteOffset and byteLength greater than ArrayBuffer byteLength'); 399 | } 400 | 401 | this.buffer = buffer; 402 | } 403 | 404 | define(DataView.prototype, { 405 | getFloat32: function getFloat32(byteOffset, littleEndian){ 406 | return readFloat(this, byteOffset, littleEndian, 23, 4); 407 | }, 408 | getFloat64: function getFloat64(byteOffset, littleEndian){ 409 | return readFloat(this, byteOffset, littleEndian, 52, 8); 410 | }, 411 | getInt8: function getInt8(byteOffset){ 412 | var n = readUint8(this, byteOffset); 413 | return n & 0x80 ? n ^ -0x100 : n; 414 | }, 415 | getInt16: function getInt16(byteOffset, littleEndian){ 416 | var n = readUint16(this, byteOffset, littleEndian); 417 | return n & 0x8000 ? n ^ -0x10000 : n; 418 | }, 419 | getInt32: function getInt32(byteOffset, littleEndian){ 420 | var n = readUint32(this, byteOffset, littleEndian); 421 | return n & 0x80000000 ? n ^ -0x100000000 : n; 422 | }, 423 | getUint8: function getUint8(byteOffset){ 424 | return readUint8(this, byteOffset); 425 | }, 426 | getUint16: function getUint16(byteOffset, littleEndian){ 427 | return readUint16(this, byteOffset, littleEndian); 428 | }, 429 | getUint32: function getUint32(byteOffset, littleEndian){ 430 | return readUint32(this, byteOffset, littleEndian); 431 | }, 432 | setFloat32: function setFloat32(byteOffset, value, littleEndian){ 433 | writeFloat(this, byteOffset, value, littleEndian, 23, 4); 434 | }, 435 | setFloat64: function setFloat64(byteOffset, value, littleEndian){ 436 | writeFloat(this, byteOffset, value, littleEndian, 52, 8); 437 | }, 438 | setInt8: function setInt8(byteOffset, value){ 439 | writeUint8(this, byteOffset, value < 0 ? value | 0x100 : value); 440 | }, 441 | setInt16: function setInt16(byteOffset, value, littleEndian){ 442 | writeUint16(this, byteOffset, value < 0 ? value | 0x10000 : value, littleEndian); 443 | }, 444 | setInt32: function setInt32(byteOffset, value, littleEndian){ 445 | writeUint32(this, byteOffset, value < 0 ? value | 0x100000000 : value, littleEndian); 446 | }, 447 | setUint8: function setUint8(byteOffset, value){ 448 | writeUint8(this, byteOffset, value); 449 | }, 450 | setUint16: function setUint16(byteOffset, value, littleEndian){ 451 | writeUint16(this, byteOffset, value, littleEndian); 452 | }, 453 | setUint32: function setUint32(byteOffset, value, littleEndian){ 454 | writeUint32(this, byteOffset, value, littleEndian); 455 | } 456 | }); 457 | 458 | return DataView; 459 | })(); 460 | }((0,eval)('this')); -------------------------------------------------------------------------------- /js/supercold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SUPERCOLD 3 | * https://import-this.github.io/supercold 4 | * 5 | * A simple and crude 2D HTML5 game with the clever mechanics of SUPERHOT. 6 | * (You can check out SUPERHOT @ http://superhotgame.com/) 7 | * 8 | * Copyright (c) 2017, Vasilis Poulimenos 9 | * Released under the BSD 3-Clause License 10 | * https://github.com/import-this/supercold/blob/master/LICENSE 11 | * 12 | * Supported browsers (as suggested by online references): 13 | * IE 9+, FF 4+, Chrome 5+, Opera 11.60+, SF 5+ 14 | * 15 | * Also, note the Phaser requirements: 16 | * https://github.com/photonstorm/phaser#requirements 17 | * especially the polyfill for IE9: 18 | * https://github.com/photonstorm/phaser#ie9 19 | * (Phaser provides some polyfills, e.g. rAF and Function.prototype.bind.) 20 | * 21 | * The code follows the conventions of Google JavaScript Style Guide, 22 | * with some alterations. The style guide is described in depth here: 23 | * https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml 24 | * Comments follow the conventions of JSDoc. Documentation can be found here: 25 | * http://usejsdoc.org/ 26 | * 27 | * Date: 7/5/2017 28 | * @version: 1.3.0 29 | * @author Vasilis Poulimenos 30 | */ 31 | 32 | /*globals Phaser */ 33 | (function(window, Phaser) { 34 | 35 | "use strict"; 36 | 37 | /** 38 | * Don't forget to set this to false in production! 39 | * @const 40 | */ 41 | var DEBUG = false; 42 | 43 | function noop() {} 44 | 45 | /******************************* Basic Logging ********************************/ 46 | 47 | /** 48 | * Handy shortcuts. 49 | * Creating the shortcuts is unfortunately tricky, because of diffs in browsers: 50 | * https://github.com/whatwg/console/issues/3 51 | * @const 52 | */ 53 | var log = function log() { 54 | Function.prototype.apply.call(console.log, console, arguments); 55 | }, 56 | warn = function warn() { 57 | Function.prototype.apply.call(console.warn, console, arguments); 58 | }, 59 | error = function error() { 60 | Function.prototype.apply.call(console.error, console, arguments); 61 | }, 62 | // Use assert only in DEBUG mode! 63 | assert = function assert(assertion) { 64 | console.assert.apply(console, arguments); 65 | // console.assert does not throw in browsers! 66 | // https://developer.mozilla.org/en-US/docs/Web/API/console/assert 67 | if (!assertion) { 68 | throw 'AssertionError'; 69 | } 70 | }; 71 | 72 | /********************************* Polyfills **********************************/ 73 | 74 | if (String.prototype.startsWith === undefined) { 75 | warn('Undefined "String.prototype.startsWith". Using really bad polyfill...'); 76 | 77 | // http://stackoverflow.com/a/4579228/1751037 78 | String.prototype.startsWith = function startsWith(prefix) { 79 | return this.lastIndexOf(prefix, 0) === 0; 80 | }; 81 | } 82 | 83 | if (Object.freeze === undefined) { 84 | warn('Undefined "Object.freeze". Using noop polyfill...'); 85 | 86 | // https://github.com/es-shims/es5-shim/blob/master/es5-sham.js#L477 87 | Object.freeze = function freeze(object) { 88 | return object; 89 | }; 90 | } 91 | 92 | /******************************* Local Storage ********************************/ 93 | 94 | /* 95 | * https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage 96 | * http://dev.w3.org/html5/webstorage/ 97 | */ 98 | 99 | /** 100 | * The version number. 101 | * Useful for marking incompatible changes in the storage format. 102 | * @const {string} 103 | */ 104 | var STORAGE_VERSION = '1.0.1'; 105 | 106 | /** 107 | * Local storage is per origin (per domain and protocol), 108 | * so use a prefix to avoid collisions with other games. 109 | * @const {string} 110 | */ 111 | var PREFIX = 'supercold_'; 112 | 113 | /** 114 | * Storage keys. 115 | * @const {object} 116 | */ 117 | var Keys = { 118 | VERSION: PREFIX + 'version', 119 | 120 | // The highest level that the player has reached. 121 | HIGHEST_LEVEL: PREFIX + 'highest_level', 122 | // The last level that the player played. 123 | LEVEL: PREFIX + 'level', 124 | TIMES: PREFIX + 'times', 125 | GUNS: PREFIX + 'guns', 126 | MUTATORS: PREFIX + 'mutators', 127 | RNDSTATE: PREFIX + 'rndstate' 128 | }; 129 | 130 | /** 131 | * @const {object} 132 | */ 133 | var MSGS = { 134 | NAN: 'Argument cannot be interpreted as a number', 135 | NEG: 'Negative argument' 136 | }; 137 | 138 | /** 139 | * A storage manager for the game. 140 | * Manages local storage. 141 | * 142 | * @param {Storage} [localStorage=window.localStorage] - The local storage. 143 | * @constructor 144 | */ 145 | function GameStorageManager(localStorage) { 146 | this.localStorage = localStorage || window.localStorage; 147 | 148 | // If the storage uses an old version, erase everything to avoid errors. 149 | if (this.load(Keys.VERSION) !== STORAGE_VERSION) { 150 | if (DEBUG) log('Clearing storage...'); 151 | this.clear(); 152 | this.save(Keys.VERSION, STORAGE_VERSION); 153 | } 154 | 155 | this.saveLevel(this.load(Keys.LEVEL) || 1); 156 | this.saveTimes(this.load(Keys.TIMES) || 0); 157 | this.save(Keys.HIGHEST_LEVEL, 158 | this.load(Keys.HIGHEST_LEVEL) || this.load(Keys.LEVEL)); 159 | } 160 | 161 | /** 162 | * Clears the local storage. 163 | */ 164 | GameStorageManager.prototype.clear = function() { 165 | var localStorage = window.localStorage, keys = [], i, key; 166 | 167 | // Iterate over the local storage keys and remove the ones with our prefix. 168 | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key 169 | for (i = 0; i < localStorage.length; ++i) { 170 | key = localStorage.key(i); 171 | if (key.startsWith(PREFIX)) { 172 | keys.push(key); 173 | } 174 | } 175 | keys.forEach(function(key, i) { 176 | localStorage.removeItem(key); 177 | }); 178 | }; 179 | 180 | GameStorageManager.prototype.save = function(key, value) { 181 | this.localStorage.setItem(key, value); 182 | }; 183 | GameStorageManager.prototype.load = function(key) { 184 | return this.localStorage.getItem(key); 185 | }; 186 | 187 | /** 188 | * Returns the highest level that the player has reached. 189 | * @return {number} The highest level. 190 | */ 191 | GameStorageManager.prototype.loadHighestLevel = function() { 192 | return Number(this.load(Keys.HIGHEST_LEVEL)); 193 | }; 194 | 195 | /** 196 | * Sets the last level that the player played. 197 | * This will also increase the highest level reached if necessary. 198 | * 199 | * @param {number} level - The new level. 200 | * @throws {TypeError} if the parameter cannot be interpreted as a number. 201 | * @throws {RangeError} if the new level is negative. 202 | */ 203 | GameStorageManager.prototype.saveLevel = function(level) { 204 | level = Number(level); 205 | if (isNaN(level)) { 206 | throw new TypeError(MSGS.NAN); 207 | } 208 | if (level < 0) { 209 | throw new RangeError(MSGS.NEG); 210 | } 211 | this.save(Keys.LEVEL, level); 212 | if (level > Number(this.load(Keys.HIGHEST_LEVEL))) { 213 | this.save(Keys.HIGHEST_LEVEL, level); 214 | } 215 | }; 216 | /** 217 | * Returns the last level that the player played. 218 | * @return {number} The level. 219 | */ 220 | GameStorageManager.prototype.loadLevel = function() { 221 | return Number(this.load(Keys.LEVEL)); 222 | }; 223 | 224 | GameStorageManager.prototype.saveTimes = function(times) { 225 | this.save(Keys.TIMES, times); 226 | }; 227 | GameStorageManager.prototype.loadTimes = function() { 228 | return Number(this.load(Keys.TIMES)); 229 | }; 230 | 231 | GameStorageManager.prototype.saveMutators = function(mutators) { 232 | this.save(Keys.MUTATORS, JSON.stringify(mutators)); 233 | }; 234 | GameStorageManager.prototype.loadMutators = function() { 235 | return JSON.parse(this.load(Keys.MUTATORS)); 236 | }; 237 | 238 | GameStorageManager.prototype.saveGuns = function(guns) { 239 | this.save(Keys.GUNS, JSON.stringify(guns)); 240 | }; 241 | GameStorageManager.prototype.loadGuns = function() { 242 | return JSON.parse(this.load(Keys.GUNS)); 243 | }; 244 | 245 | GameStorageManager.prototype.saveRndState = function(state) { 246 | this.save(Keys.RNDSTATE, state); 247 | }; 248 | GameStorageManager.prototype.loadRndState = function() { 249 | return this.load(Keys.RNDSTATE); 250 | }; 251 | 252 | /********************************* Supercold **********************************/ 253 | 254 | /** 255 | * The game version. Not really used for anything right now. 256 | * @const 257 | */ 258 | var VERSION = '1.3.0'; 259 | 260 | log('%c\n' + 261 | ' _____________ __________________________________ _________ ________ .____ ________ \n' + 262 | ' / _____/ | \\______ \\_ _____/\\______ \\ \\_ ___ \\ \\_____ \\ | | \\______ \\ \n' + 263 | ' \\_____ \\| | /| ___/| __)_ | _/ / \\ \\/ / | \\| | | | \\ \n' + 264 | ' / \\ | / | | | \\ | | \\ \\ \\____/ | \\ |___ | ` \\ \n' + 265 | ' /_______ /______/ |____| /_______ / |____|_ / \\______ /\\_______ /_______ \\/_______ / \n' + 266 | ' \\/ \\/ \\/ \\/ \\/ \\/ \\/ \n\n', 267 | 'font-family: monospace'); 268 | log('Version:', VERSION); 269 | log('Welcome to SUPERCOLD! Hope you enjoy! :)\n\n'); 270 | 271 | if (DEBUG) { 272 | warn('Running in DEBUG mode! Do not forget to disable in production!\n\n'); 273 | } 274 | 275 | /** 276 | * Some module-level global settings/constants for our Phaser game. 277 | * @const 278 | */ 279 | var CLEAR_WORLD = true, 280 | CLEAR_CACHE = true, 281 | ADD_TO_CACHE = true, 282 | ADD_TO_STAGE = true, 283 | AUTOSTART = true, 284 | EXISTS = true, 285 | CREATE_IF_NULL = true, 286 | USE_WORLD_COORDS = true, 287 | /** 288 | * Use the more expensive but powerful P2 physics system. The others don't cut it. 289 | * Note: When a game object is given a P2 body, it has its anchor set to 0.5. 290 | */ 291 | PHYSICS_SYSTEM = Phaser.Physics.P2JS, 292 | 293 | /** 294 | * Constants for the Phaser.Cache. 295 | */ 296 | CACHE = { 297 | /** 298 | * Identifiers for assets in the Phaser.Cache. 299 | */ 300 | KEY: { 301 | PLAYER: 'player', 302 | BOT: 'bot', 303 | THROWABLE: 'throwable', 304 | BULLET: 'bullet', 305 | TRAIL: 'trail', 306 | BG: 'background', 307 | FLASH: 'flash', 308 | OVERLAY_DARK: 'overlay_dark', 309 | OVERLAY_LIGHT: 'overlay_light' 310 | } 311 | }, 312 | 313 | DEBUG_POSX = 32; 314 | 315 | /* 316 | * Note: All dimensions/lengths/distances are measured in pixels. 317 | * It's probably best to keep all these values round. 318 | */ 319 | 320 | /** 321 | * The screen ratio for most devices is 16/9, 322 | * so let's pick a corresponding resolution. 323 | * http://www.w3schools.com/browsers/browsers_display.asp 324 | * https://www.w3counter.com/globalstats.php 325 | * @const 326 | */ 327 | var NATIVE_WIDTH = 1366, 328 | NATIVE_HEIGHT = 768, 329 | // Enough padding so that the player is always centered. 330 | PADDING = { 331 | width: Math.round(NATIVE_WIDTH / 2), 332 | height: Math.round(NATIVE_HEIGHT / 2) 333 | }, 334 | 335 | CELLDIM = 40, 336 | 337 | FACTOR_SLOW = 16, 338 | FACTOR_SLOWER = 42, 339 | FACTOR_SLOWEST = 192, 340 | 341 | PLAYER_SPEED = 300, 342 | BOT_SPEED_NORMAL = PLAYER_SPEED, 343 | BULLET_SPEED_NORMAL = PLAYER_SPEED * 5; 344 | 345 | 346 | /** 347 | * The Supercold namespace. 348 | * @namespace 349 | */ 350 | var Supercold = { 351 | /** 352 | * Cell dimensions (square). 353 | */ 354 | cell: { 355 | width: CELLDIM 356 | // Same width and height. 357 | }, 358 | /** 359 | * Supercold world size (aspect ratio 4:3). 360 | */ 361 | world: { 362 | //width: CELLDIM * 64, // 2560 363 | //height: CELLDIM * 48 // 1920 364 | //width: CELLDIM * 60, // 2400 365 | //height: CELLDIM * 42 // 1800 366 | width: CELLDIM * 56, // 2240 367 | height: CELLDIM * 42 // 1680 368 | }, 369 | 370 | /** 371 | * Sprite properties. 372 | * 373 | * Note: 374 | * To avoid single-pixel jitters on mobile devices, it is strongly 375 | * recommended to use Sprite sizes that are even on both axis. 376 | */ 377 | player: { 378 | radius: 25 379 | }, 380 | throwable: { 381 | radius: 12 382 | }, 383 | bullet: { 384 | width: 16, 385 | height: 8, 386 | bodyLen: 8 387 | }, 388 | minimap: { 389 | x: 10, // The x-coordinate of the right of the minimap. 390 | y: 10, // The y-coordinate of the bottom of the minimap. 391 | width: 200, 392 | height: 150 393 | }, 394 | bar: { 395 | x: 10, // The x-coordinate of the right of the bar. 396 | // y relative to minimap! 397 | width: 200, 398 | height: 10 399 | }, 400 | bulletCount: { 401 | x: 10, 402 | y: 10 403 | }, 404 | 405 | speeds: { 406 | player: PLAYER_SPEED, 407 | dodge: PLAYER_SPEED * 2, 408 | bot: { 409 | normal: PLAYER_SPEED, 410 | slow: Math.round(BOT_SPEED_NORMAL / FACTOR_SLOW), 411 | slower: Math.round(BOT_SPEED_NORMAL / FACTOR_SLOWER), 412 | slowest: Math.round(BOT_SPEED_NORMAL / FACTOR_SLOWEST) 413 | }, 414 | bullet: { 415 | normal: BULLET_SPEED_NORMAL, 416 | slow: Math.round(BULLET_SPEED_NORMAL / FACTOR_SLOW), 417 | slower: Math.round(BULLET_SPEED_NORMAL / FACTOR_SLOWER), 418 | slowest: Math.round(BULLET_SPEED_NORMAL / FACTOR_SLOWEST) 419 | } 420 | }, 421 | 422 | baseFireRate: 1 / 2, // 2 times / sec 423 | fastFireFactor: 1 / 2, // Twice as fast 424 | initFireDelay: 1 / 8, 425 | 426 | hotswitchTimeout: 2.5, 427 | superhotswitchTimeout: 0.5, 428 | 429 | bigheadScale: 1.5, 430 | chibiScale: 0.667, 431 | 432 | /** 433 | * Styling options. 434 | */ 435 | style: { 436 | stage: { 437 | backgroundColor: 'rgb(10, 10, 10)' 438 | }, 439 | background: { 440 | lightColor: 'rgb(150, 150, 150)', 441 | darkColor: 'rgb(64, 64, 64)' 442 | }, 443 | overlay: { 444 | lightColor: 'rgba(32, 32, 32, 0.475)', 445 | darkColor: 'rgba(0, 0, 0, 0.675)' 446 | }, 447 | player: { 448 | color: 'rgb(34, 34, 34)', 449 | strokeStyle: 'rgb(50, 48, 48)', 450 | lineWidth: 6, 451 | shadowOffsetX: 0, 452 | shadowOffsetY: 0, 453 | shadowColor: 'rgba(242, 238, 238, 0.95)', 454 | shadowBlur: 12 455 | }, 456 | bot: { 457 | color: 'rgb(255, 34, 33)', 458 | strokeStyle: 'rgb(44, 34, 34)', 459 | lineWidth: 6, 460 | shadowOffsetX: 0, 461 | shadowOffsetY: 0, 462 | shadowColor: 'rgba(255, 34, 33, 0.95)', 463 | shadowBlur: 8 464 | }, 465 | minimap: { 466 | background: { 467 | color: 'rgba(40, 40, 40, 0.5)' 468 | }, 469 | border: { 470 | color: 'rgba(64, 64, 64, 0.85)', 471 | lineWidth: 5 472 | }, 473 | innerBorder: { 474 | color: 'rgba(64, 64, 64, 0.75)', 475 | lineWidth: 2.5 476 | }, 477 | player: { 478 | radius: 4, 479 | color: 'rgb(34, 34, 34)', 480 | strokeStyle: 'rgba(48, 48, 48, 0.95)', 481 | lineWidth: 1.5 482 | }, 483 | bot: { 484 | radius: 4, 485 | color: 'rgb(255, 34, 33)', 486 | strokeStyle: 'rgba(34, 34, 34, 0.95)', 487 | lineWidth: 1.5 488 | }, 489 | throwable: { 490 | radius: 2, 491 | color: 'rgb(30, 35, 38)' 492 | } 493 | }, 494 | bulletBar: { 495 | color: 'rgba(25, 28, 30, 0.9)' 496 | }, 497 | hotswitchBar: { 498 | color: 'rgba(250, 0, 0, 0.9)' 499 | }, 500 | throwable: { 501 | color: 'rgb(30, 35, 38)' 502 | }, 503 | bullet: { 504 | color: 'rgb(30, 35, 38)', 505 | markColor: 'rgba(140, 140, 140, 0.95)' 506 | }, 507 | trail: { 508 | x1: 0, 509 | y1: 0, 510 | x2: 500, 511 | y2: 0, 512 | colorStops: [ 513 | { stop: 0.0, color: 'rgba(255, 34, 33, 0.00)' }, 514 | { stop: 0.1, color: 'rgba(255, 34, 33, 0.10)' }, 515 | { stop: 0.8, color: 'rgba(255, 34, 33, 0.85)' }, 516 | { stop: 0.9, color: 'rgba(255, 34, 33, 0.95)' }, 517 | { stop: 1.0, color: 'rgba(255, 34, 33, 0.85)' } 518 | ], 519 | shadowOffsetX: 0, 520 | shadowOffsetY: 0, 521 | shadowColor: 'rgba(255, 34, 33, 0.85)', 522 | shadowBlur: 2 523 | }, 524 | grid: { 525 | color1: 'rgba(250, 240, 230, 1)', 526 | color2: 'rgba(230, 240, 250, 1)', 527 | colorOuter: 'rgba(240, 35, 34, 1)', 528 | shadowOffsetX: 0, 529 | shadowOffsetY: 0, 530 | shadowColor1: 'rgba(240, 240, 255, 0.5)', 531 | shadowColor2: 'rgba(240, 240, 255, 0.5)', 532 | shadowColorOuter: 'rgba(255, 34, 33, 1)', 533 | shadowBlur: 1 534 | }, 535 | lines: { 536 | color: 'rgba(42, 65, 71, 0.0975)', 537 | lineWidth: 4, 538 | shadowOffsetX: 0, 539 | shadowOffsetY: 0, 540 | shadowColor: 'rgba(226, 220, 220, 0.75)', 541 | shadowBlur: 2, 542 | angle: Math.PI / 4 543 | }, 544 | /* spots: [{ 545 | colorStops: [ 546 | { stop: 0.00, color: 'rgba(254, 254, 254, 0.90)' }, 547 | { stop: 1.00, color: 'rgba(191, 234, 240, 0.00)' } 548 | ] 549 | }, { 550 | colorStops: [ 551 | { stop: 0.00, color: 'rgba(17, 15, 18, 0.60)' }, 552 | { stop: 0.25, color: 'rgba(33, 60, 71, 0.60)' }, 553 | { stop: 1.00, color: 'rgba(73, 142, 157, 0.05)' }, 554 | ] 555 | }], */ 556 | flash: { 557 | colorStops: [ 558 | { stop: 0.0, color: 'rgba(255, 255, 255, 0.3)' }, 559 | { stop: 0.4, color: 'rgba(245, 255, 255, 0.6)' }, 560 | { stop: 0.5, color: 'rgba(240, 251, 255, 0.7)' }, 561 | { stop: 0.6, color: 'rgba(245, 255, 255, 0.8)' }, 562 | { stop: 1.0, color: 'rgba(255, 255, 255, 1.0)' } 563 | ] 564 | }, 565 | superhot: { 566 | font: 'Arial', 567 | fontWeight: 'normal', 568 | fill: 'rgb(245, 251, 255)', 569 | stroke: 'rgba(245, 251, 255, 0.5)', 570 | strokeThickness: 1, 571 | boundsAlignH: 'center', 572 | boundsAlignV: 'middle', 573 | shadowOffsetX: 0, 574 | shadowOffsetY: 0, 575 | shadowColor: 'rgba(215, 251, 255, 0.85)', 576 | shadowBlur: 16 577 | } 578 | }, 579 | /** 580 | * Custom styling options for individual words. 581 | */ 582 | wordStyles: { 583 | 'SUPER': { 584 | fontWeight: 'lighter', 585 | shadowBlur: 24, 586 | shadowColor: 'rgba(210, 250, 255, 0.9)' 587 | }, 588 | 'HOT': { 589 | fontWeight: 'bolder', 590 | shadowColor: 'rgba(215, 251, 255, 0.925)', 591 | shadowBlur: 28 592 | }, 593 | 'COLD': { 594 | fontWeight: 'bolder', 595 | shadowColor: 'rgba(210, 250, 255, 0.95)', 596 | shadowBlur: 32 597 | }, 598 | 'YOU': { 599 | fontWeight: 'bold', 600 | //fill: 'rgb(249, 19, 21)', 601 | //shadowColor: 'rgba(251, 81, 81, 0.95)' 602 | fill: 'rgb(34, 34, 34)', 603 | stroke: 'rgba(34, 34, 34, 0.5)', 604 | shadowColor: 'rgba(48, 48, 48, 0.95)', 605 | shadowBlur: 24 606 | }, 607 | 'LEVEL': { 608 | fontWeight: 'bold' 609 | }, 610 | 'BULLETS': { 611 | font: 'Arial', 612 | fontWeight: 'normal', 613 | fontSize: 18, 614 | fill: 'rgb(245, 251, 255)', 615 | stroke: 'rgba(245, 251, 255, 0.5)', 616 | strokeThickness: 1, 617 | boundsAlignH: 'center', 618 | boundsAlignV: 'middle' 619 | } 620 | }, 621 | 622 | /** 623 | * Keyboard Controls. 624 | */ 625 | controls: { 626 | WASD: { 627 | 'up': Phaser.KeyCode.W, 628 | 'left': Phaser.KeyCode.A, 629 | 'down': Phaser.KeyCode.S, 630 | 'right': Phaser.KeyCode.D 631 | }, 632 | fireKey: Phaser.KeyCode.SPACEBAR, 633 | dodgeKey: Phaser.KeyCode.SHIFT, 634 | quitKey: Phaser.KeyCode.ESC, 635 | restartKey: Phaser.KeyCode.T 636 | }, 637 | 638 | texts: { 639 | SUPERHOT: 'SUPER HOT'.split(' '), 640 | SUPERCOLD: 'SUPER COLD'.split(' '), 641 | MECHANICS: 'TIME MOVES WHEN YOU MOVE'.split(' '), 642 | // These are special cases! 643 | LEVEL: 'LEVEL', 644 | BULLETS: 'Bullets: ' 645 | } 646 | }; 647 | 648 | // Freeze objects to detect erroneous overriding. 649 | (function recursiveFreeze(obj) { 650 | var prop; 651 | 652 | for (prop in obj) { 653 | if (typeof obj[prop] === 'object') { 654 | Object.freeze(obj[prop]); 655 | recursiveFreeze(obj[prop]); 656 | } 657 | } 658 | }(Supercold)); 659 | 660 | 661 | Supercold.GameStorageManager = GameStorageManager; 662 | 663 | /******************************* Utilities ********************************/ 664 | 665 | function getWordStyle(word) { 666 | var defaultStyle = Supercold.style.superhot, 667 | // In case of phrase instead of word, use the first word as index. 668 | wordStyle = Supercold.wordStyles[word.split(' ')[0]]; 669 | 670 | if (wordStyle) { 671 | return Phaser.Utils.extend({}, defaultStyle, wordStyle); 672 | } else { 673 | return defaultStyle; 674 | } 675 | } 676 | 677 | 678 | /** 679 | * Sets the scale object specified such that it will fill the screen, but 680 | * crop some of the top/bottom or left/right sides of the playing field. 681 | * http://stackoverflow.com/a/33517425/1751037 682 | * 683 | * @param {Phaser.Point} scale - the scale object to set 684 | * @param {number} width - the target width 685 | * @param {number} height - the target height 686 | * @param {number} [nativeWidth=NATIVE_WIDTH] - the native width of the game 687 | * @param {number} [nativeHeight=NATIVE_HEIGHT] - the native height of the game 688 | */ 689 | function scaleToFill(scale, width, height, nativeWidth, nativeHeight) { 690 | nativeWidth = nativeWidth || NATIVE_WIDTH; 691 | nativeHeight = nativeHeight || NATIVE_HEIGHT; 692 | scale.setTo(Math.max(width / nativeWidth, height / nativeHeight)); 693 | } 694 | 695 | /** 696 | * Sets the scale object specified such that it will fit the screen, 697 | * but have some extra space on the top/bottom or left/right sides. 698 | * http://stackoverflow.com/a/33517425/1751037 699 | * 700 | * @param {Phaser.Point} scale - the scale object to set 701 | * @param {number} width - the target width 702 | * @param {number} height - the target height 703 | * @param {number} [nativeWidth=NATIVE_WIDTH] - the native width of the game 704 | * @param {number} [nativeHeight=NATIVE_HEIGHT] - the native height of the game 705 | */ 706 | function scaleToFit(scale, width, height, nativeWidth, nativeHeight) { 707 | nativeWidth = nativeWidth || NATIVE_WIDTH; 708 | nativeHeight = nativeHeight || NATIVE_HEIGHT; 709 | scale.setTo(Math.min(width / nativeWidth, height / nativeHeight)); 710 | } 711 | 712 | /** 713 | * Puts the given sprite in the center of the camera view/screen. 714 | * If the sprite is fixed to camera, it sets the camera offset, too. 715 | * @param {Phaser.Camera} camera - A reference to the game camera. 716 | * @param {Phaser.Sprite} sprite - The sprite to center. 717 | */ 718 | function centerToCamera(camera, sprite) { 719 | sprite.centerX = camera.view.halfWidth; 720 | sprite.centerY = camera.view.halfHeight; 721 | if (sprite.fixedToCamera) { 722 | sprite.cameraOffset.set(sprite.x, sprite.y); 723 | } 724 | if (DEBUG) { 725 | log('Centered to camera', (sprite.name) ? sprite.name : 'sprite'); 726 | } 727 | } 728 | 729 | 730 | function _resize() { 731 | /*jshint validthis: true */ 732 | centerToCamera(this.game.camera, this); 733 | } 734 | 735 | /** 736 | * An image that is always centered in the camera view. 737 | * @constructor 738 | */ 739 | function CenteredImage(game, key) { 740 | Phaser.Image.call(this, game, 0, 0, key); 741 | 742 | this.name = 'CenteredImage'; 743 | this.anchor.set(0.5); 744 | 745 | // Center it. 746 | this.resize(); 747 | } 748 | 749 | CenteredImage.prototype = Object.create(Phaser.Image.prototype); 750 | CenteredImage.prototype.constructor = CenteredImage; 751 | 752 | CenteredImage.prototype.resize = _resize; 753 | 754 | /** 755 | * Text that is always centered in the camera view. 756 | * @constructor 757 | */ 758 | function CenteredText(game, text, style) { 759 | Phaser.Text.call(this, game, 0, 0, text, style); 760 | 761 | this.name = '"' + text + '"'; 762 | this.anchor.set(0.5); 763 | 764 | // Center it. 765 | this.resize(); 766 | } 767 | 768 | CenteredText.prototype = Object.create(Phaser.Text.prototype); 769 | CenteredText.prototype.constructor = CenteredText; 770 | 771 | CenteredText.prototype.resize = _resize; 772 | 773 | /** 774 | * Text that is scaled and always centered in the camera view. 775 | * @constructor 776 | */ 777 | function ScaledCenteredText(game, text, style) { 778 | style = Phaser.Utils.extend({}, style); 779 | style.fontSize = this._getFontSize(game, text); 780 | CenteredText.call(this, game, text, style); 781 | } 782 | 783 | ScaledCenteredText.prototype = Object.create(CenteredText.prototype); 784 | ScaledCenteredText.prototype.constructor = ScaledCenteredText; 785 | 786 | /** 787 | * Returns an appropriate font size, based on the game width and the text length. 788 | */ 789 | ScaledCenteredText.prototype._getFontSize = function(game, text) { 790 | // The font size specifies height for a character, so approximate the width. 791 | // fontSize = charHeight ~ charWidth * 4/3 ~ game.width / textWidth * 4/3 792 | return (game.width / text.length * 4/3) + 'px'; 793 | }; 794 | 795 | ScaledCenteredText.prototype._scaleTextToFit = function() { 796 | // Force the text to always fit the screen (factor * game.width/height). 797 | // Use text.texture, since text.width/height takes into account scaling. 798 | // Notice the factor for text.height. Height measuring for text is not 799 | // accurate, so Phaser ends up with a height larger than the actual one. 800 | scaleToFit(this.scale, 801 | Math.round(0.9 * this.game.width), 802 | Math.round(0.9 * this.game.height), 803 | this.texture.width, 804 | Math.round(this.texture.height / 1.4)); 805 | }; 806 | 807 | ScaledCenteredText.prototype.resize = function() { 808 | var style = getWordStyle(this.text), 809 | scale = this.game.camera.scale; 810 | 811 | this.fontSize = this._getFontSize(this.game, this.text); 812 | // shadowOffsetX/Y is NOT affected by the current transformation matrix! 813 | // shadowBlur does NOT correspond to a number of pixels and is NOT affected 814 | // by the current transformation matrix! Use world.scale as an approximation. 815 | this.setShadow( 816 | style.shadowOffsetX * scale.x, style.shadowOffsetY * scale.y, 817 | style.shadowColor, style.shadowBlur * scale.x); 818 | this._scaleTextToFit(); 819 | centerToCamera(this.game.camera, this); 820 | }; 821 | 822 | 823 | function getOverlayBitmap(game, color) { 824 | switch (color) { 825 | case 'light': 826 | return game.cache.getBitmapData(CACHE.KEY.OVERLAY_LIGHT); 827 | case 'dark': 828 | /* falls through */ 829 | default: 830 | return game.cache.getBitmapData(CACHE.KEY.OVERLAY_DARK); 831 | } 832 | } 833 | 834 | function newOverlay(game, color) { 835 | return new CenteredImage(game, getOverlayBitmap(game, color)); 836 | } 837 | 838 | 839 | /** 840 | * An object that displays messages to the player. 841 | * 842 | * @param {Array.} text - The message (word/phrase) to display. 843 | * @param {object} [options] - A customization object. 844 | * @param {Phaser.Group} [options.group=Game.World] - 845 | * The group in which to add the announcer's objects. 846 | * @param {number} [options.initDelay=0] - 847 | * How long to wait before starting. 848 | * @param {number} [options.nextDelay=550] - 849 | * How long to wait before showing the next word. 850 | * @param {number} [options.finalDelay=options.nextDelay] - 851 | * How long to wait before showing the message again. 852 | * @param {number} [options.duration=450] - 853 | * How long the animation for each word will last. 854 | * @param {number} [options.flashOnDuration=25] - 855 | * How long it will take to turn the flash on. 856 | * @param {number} [options.flashOffDuration=400] - 857 | * How long it will take to turn the flash off. 858 | * @param {number} [options.flashTint=0xFFFFFF] - 859 | * A tint to change the color of the flash. 860 | * @param {boolean} [options.repeat=false] - 861 | * If true, repeat the message forever. 862 | * @param {boolean} [options.overlay=false] - 863 | * Add a layer between the game and the text. 864 | * @param {string} [options.overlayColor='dark'] - 865 | * Specify a 'light' or 'dark' shade for the overlay. 866 | * @param {function} [options.onComplete=noop] - 867 | * A function to call once the announcer is done. 868 | * @constructor 869 | */ 870 | function Announcer(game, text, options) { 871 | var group = options.group || game.world; 872 | 873 | this.options = Phaser.Utils.extend({}, Announcer.defaults, options); 874 | if (this.options.finalDelay === undefined) { 875 | this.options.finalDelay = this.options.nextDelay; 876 | } 877 | 878 | if (this.options.overlay) { 879 | this._overlay = group.add( 880 | newOverlay(game, this.options.overlayColor)); 881 | this._overlay.name = 'Announcer overlay'; 882 | } else { 883 | // Add a null object. 884 | this._overlay = new CenteredImage(game); 885 | this._overlay.name = 'Announcer null overlay'; 886 | } 887 | 888 | this._flash = group.add(new CenteredImage( 889 | game, game.cache.getBitmapData(CACHE.KEY.FLASH))); 890 | this._flash.name = 'Announcer flash'; 891 | this._flash.alpha = 0; 892 | this._flash.tint = this.options.flashTint; 893 | 894 | this._textGroup = Announcer._addTextGroup(game, group, text); 895 | 896 | this._inWorldGroup = (group === game.world); 897 | 898 | this._textTween = null; 899 | this._timer = null; 900 | 901 | // Handy references. 902 | this.camera = game.camera; 903 | this.add = game.add; 904 | this.time = game.time; 905 | 906 | // Objects in the game world are subject to camera positioning and scaling. 907 | if (this._inWorldGroup) { 908 | this._overlay.fixedToCamera = true; 909 | this._flash.fixedToCamera = true; 910 | this._textGroup.forEach(function(text) { 911 | text.fixedToCamera = true; 912 | }); 913 | this.resize(); 914 | } 915 | } 916 | 917 | Announcer.defaults = { 918 | initDelay: 0, 919 | nextDelay: 550, 920 | //finalDelay: , 921 | duration: 450, 922 | flashOnDuration: 25, 923 | flashOffDuration: 400, 924 | flashTint: 0xFFFFFF, 925 | repeat: false, 926 | overlay: false, 927 | overlayColor: 'dark', 928 | onComplete: noop 929 | }; 930 | 931 | Announcer.scaleFactor = 1.06; 932 | 933 | Announcer.prototype.resize = function() { 934 | var scale = this.camera.scale; 935 | 936 | // Do nothing if the announcer has finished. 937 | if (this._flash === null) return; 938 | 939 | if (this._inWorldGroup) { 940 | // Account for camera scaling. 941 | // Note: World and camera scaling is the same in Phaser. 942 | this._overlay.scale.set(1 / scale.x, 1 / scale.y); 943 | this._flash.scale.set(1 / scale.x, 1 / scale.y); 944 | } 945 | this._overlay.resize(); 946 | this._flash.resize(); 947 | 948 | this._textGroup.forEach(function resize(text) { 949 | text.resize(); 950 | if (this._inWorldGroup) { 951 | // Undo the world/camera scaling that will be applied by Phaser. 952 | // Note: World and camera scaling values are the same in Phaser. 953 | Phaser.Point.divide(text.scale, scale, text.scale); 954 | } 955 | }, this); 956 | if (DEBUG) log('Resized announcer.'); 957 | }; 958 | 959 | /** 960 | * Stops the announcer and destroys all its components. 961 | */ 962 | Announcer.prototype.stop = function() { 963 | this.time.events.remove(this._timer); // May be null. 964 | this._textTween.stop(); 965 | 966 | // Tweens are removed from sprites/images automatically. 967 | this._textGroup.destroy(); 968 | this._flash.destroy(); 969 | this._overlay.destroy(); 970 | 971 | this._textGroup = null; 972 | this._flash = null; 973 | this._overlay = null; 974 | }; 975 | 976 | Announcer.prototype._flashCamera1 = function() { 977 | this.add.tween(this._flash).to({ 978 | alpha: 0 979 | }, this.options.flashOffDuration, Phaser.Easing.Quadratic.Out, AUTOSTART); 980 | }; 981 | 982 | Announcer.prototype._flashCamera0 = function() { 983 | this.add.tween(this._flash).to({ 984 | alpha: 1 985 | }, this.options.flashOnDuration, Phaser.Easing.Quadratic.In, AUTOSTART) 986 | .onComplete.addOnce(this._flashCamera1, this); 987 | }; 988 | 989 | Announcer.prototype._next = function() { 990 | this._textGroup.cursor.kill(); 991 | this._textGroup.next(); 992 | this._announce(); 993 | }; 994 | 995 | Announcer.prototype._repeat = function() { 996 | if (this.options.repeat) { 997 | this._next(); 998 | } else { 999 | this.stop(); 1000 | this.options.onComplete.call(this); 1001 | } 1002 | }; 1003 | 1004 | Announcer.prototype._announceNext = function() { 1005 | if (this._textGroup.cursorIndex < this._textGroup.length - 1) { 1006 | this._timer = this.time.events.add(this.options.nextDelay, this._next, this); 1007 | } else { 1008 | this._timer = this.time.events.add(this.options.finalDelay, this._repeat, this); 1009 | } 1010 | }; 1011 | 1012 | Announcer.prototype._announce = function() { 1013 | var text = this._textGroup.cursor, 1014 | oldScaleX = text.scale.x, 1015 | oldScaleY = text.scale.y; 1016 | 1017 | // Set the new scale that will be undone by the tween. 1018 | text.scale.multiply(Announcer.scaleFactor, Announcer.scaleFactor); 1019 | text.revive(); 1020 | 1021 | // DON'T tween the font size, since it will have to redraw the text sprite! 1022 | this._textTween = this.add.tween(text.scale).to({ 1023 | x: oldScaleX, 1024 | y: oldScaleY, 1025 | }, this.options.duration, Phaser.Easing.Quadratic.Out, !AUTOSTART); 1026 | this._textTween.onStart.addOnce(this._flashCamera0, this); 1027 | this._textTween.onComplete.addOnce(this._announceNext, this); 1028 | this._textTween.start(); 1029 | }; 1030 | 1031 | /** 1032 | * Displays the message to the player. 1033 | * @return {Announcer} this announcer, for possible method chaining. 1034 | */ 1035 | Announcer.prototype.announce = function() { 1036 | // NOTE: Use a timer to delay the first tween (instead of the tween delay arg)! 1037 | // This will make sure that the first word will be displayed even when lagging. 1038 | this._timer = this.time.events.add(this.options.initDelay, this._announce, this); 1039 | return this; 1040 | }; 1041 | 1042 | Announcer._addTextGroup = function(game, parent, words) { 1043 | var scale = game.camera.scale, 1044 | group, text, i, word, style; 1045 | 1046 | group = game.add.group(parent, 'Text group: "' + words + '"'); 1047 | // Due to the way the 'fixedToCamera' property works, set it for each 1048 | // text object individually (instead of setting it for the group). 1049 | // https://phaser.io/docs/2.6.2/Phaser.Sprite.html#cameraOffset 1050 | for (i = 0; i < words.length; ++i) { 1051 | word = words[i]; 1052 | style = getWordStyle(word); 1053 | 1054 | text = group.add(new ScaledCenteredText(game, word, style)); 1055 | // shadowOffsetX/Y is NOT affected by the current transformation matrix! 1056 | // shadowBlur does NOT correspond to a number of pixels and is NOT affected 1057 | // by the current transformation matrix! Use world.scale as an approximation. 1058 | text.setShadow( 1059 | style.shadowOffsetX * scale.x, style.shadowOffsetY * scale.y, 1060 | style.shadowColor, style.shadowBlur * scale.x); 1061 | // Initially invisible. 1062 | text.kill(); 1063 | } 1064 | return group; 1065 | }; 1066 | 1067 | 1068 | /** 1069 | * Shows a tip to the player. 1070 | */ 1071 | function showTip() { 1072 | var tiplist = document.getElementById('tips'), 1073 | tips = tiplist.querySelectorAll('.tip'), 1074 | tip = tips[Math.floor(Math.random() * tips.length)]; 1075 | 1076 | tip.style.display = 'block'; 1077 | tiplist.style.display = 'block'; 1078 | document.getElementById('hint').style.display = 'block'; 1079 | } 1080 | 1081 | /** 1082 | * Shows a tip to the player probabilistically. 1083 | */ 1084 | function showTipRnd(chance) { 1085 | if (chance === undefined) chance = 0.75; 1086 | if (Math.random() < chance) { 1087 | showTip(); 1088 | } else { 1089 | document.getElementById('hint').style.display = 'block'; 1090 | } 1091 | } 1092 | 1093 | /** 1094 | * Hides any tip previously shown to the player. 1095 | */ 1096 | function hideTip() { 1097 | var tiplist = document.getElementById('tips'); 1098 | 1099 | tiplist.style.display = 'none'; 1100 | Array.prototype.forEach.call(tiplist.querySelectorAll('.tip'), function(tip) { 1101 | tip.style.display = 'none'; 1102 | }); 1103 | document.getElementById('hint').style.display = 'none'; 1104 | } 1105 | 1106 | /***************************** Scalable State *****************************/ 1107 | 1108 | /** 1109 | * A state that supports a scalable viewport. 1110 | * The currect scale factor is the same as world.scale 1111 | * (which in turn is the same as camera.scale in Phaser). 1112 | * @constructor 1113 | */ 1114 | Supercold._ScalableState = function(game) {}; 1115 | 1116 | /** 1117 | * Sets the world size. 1118 | */ 1119 | Supercold._ScalableState.prototype._setWorldBounds = function() { 1120 | var width = Supercold.world.width + 2*PADDING.width, 1121 | height = Supercold.world.height + 2*PADDING.height; 1122 | 1123 | // The world is a large fixed size space with (0, 0) in the center. 1124 | this.world.setBounds( 1125 | -Math.round(width / 2), -Math.round(height / 2), width, height); 1126 | if (DEBUG) log('Set world bounds to', this.world.bounds); 1127 | }; 1128 | 1129 | /** 1130 | * Sets the world scale so that the player sees about the same portion of the 1131 | * playing field. The strategy selected scales the game so as to fill the screen, 1132 | * but it will crop some of the top/bottom or left/right sides of the playing field. 1133 | * Note: World and Camera scaling values are the same in Phaser! 1134 | */ 1135 | Supercold._ScalableState.prototype._setWorldScale = function(width, height) { 1136 | scaleToFill(this.world.scale, width, height); 1137 | if (DEBUG) log('Set world scale to', this.world.scale); 1138 | }; 1139 | 1140 | /** 1141 | * Sets all the necessary sizes and scale factors for our world. 1142 | */ 1143 | Supercold._ScalableState.prototype.setScaling = function() { 1144 | // Note: Don't use Phaser.ScaleManager.onSizeChange, since the callback 1145 | // may be triggered multiple times. Phaser.State.resize works better. 1146 | // https://phaser.io/docs/2.6.2/Phaser.ScaleManager.html#onSizeChange 1147 | this._setWorldBounds(); 1148 | this._setWorldScale(this.game.width, this.game.height); 1149 | if (DEBUG) log('Set scaling.'); 1150 | }; 1151 | 1152 | /** 1153 | * Since our game is set to Scalemode 'RESIZE', this method will be called 1154 | * automatically to handle resizing. Since subclasses of the ScalableState 1155 | * inherit this method, they are able to handle resizing, too. 1156 | * 1157 | * @param {number} width - the new width 1158 | * @param {number} height - the new height 1159 | * @override 1160 | */ 1161 | Supercold._ScalableState.prototype.resize = function(width, height) { 1162 | this._setWorldScale(width, height); 1163 | if (DEBUG) log('Resized game.'); 1164 | }; 1165 | 1166 | /** 1167 | * Rescales the sprite given to account for the scale of the world. 1168 | */ 1169 | Supercold._ScalableState.prototype.rescale = function(sprite) { 1170 | sprite.scale.set(1 / this.world.scale.x, 1 / this.world.scale.y); 1171 | }; 1172 | 1173 | /******************************* Base State *******************************/ 1174 | 1175 | /** 1176 | * State used as a base class for almost all other game states. 1177 | * Provides methods for scaling the game, handling drawing of all sprites 1178 | * and redrawing when resizing, debugging and other common functionality. 1179 | * @constructor 1180 | */ 1181 | Supercold._BaseState = function(game) { 1182 | Supercold._ScalableState.call(this, game); 1183 | }; 1184 | 1185 | Supercold._BaseState.prototype = Object.create(Supercold._ScalableState.prototype); 1186 | Supercold._BaseState.prototype.constructor = Supercold._BaseState; 1187 | 1188 | /** 1189 | * Returns the smallest even integer greater than or equal to the number. 1190 | */ 1191 | function even(n) { 1192 | n = Math.ceil(n); 1193 | return n + n%2; 1194 | } 1195 | 1196 | /** 1197 | * Draws a circle in the context specified. 1198 | */ 1199 | function circle(ctx, x, y, radius) { 1200 | ctx.beginPath(); 1201 | ctx.arc(x, y, radius, 0, 2 * Math.PI); 1202 | ctx.closePath(); 1203 | } 1204 | 1205 | /** 1206 | * Draws a line in the context specified. 1207 | */ 1208 | function line(ctx, x1, y1, x2, y2) { 1209 | ctx.beginPath(); 1210 | ctx.moveTo(x1, y1); 1211 | ctx.lineTo(x2, y2); 1212 | ctx.stroke(); 1213 | } 1214 | 1215 | function addColorStops(grd, colorStops) { 1216 | var i, colorStop; 1217 | 1218 | for (i = 0; i < colorStops.length; ++i) { 1219 | colorStop = colorStops[i]; 1220 | grd.addColorStop(colorStop.stop, colorStop.color); 1221 | } 1222 | return grd; 1223 | } 1224 | 1225 | function createLinearGradient(ctx, x1, y1, x2, y2, colorStops) { 1226 | return addColorStops( 1227 | ctx.createLinearGradient(x1, y1, x2, y2), colorStops); 1228 | } 1229 | 1230 | function createRadialGradient(ctx, x1, y1, r1, x2, y2, r2, colorStops) { 1231 | return addColorStops( 1232 | ctx.createRadialGradient(x1, y1, r1, x2, y2, r2), colorStops); 1233 | } 1234 | 1235 | 1236 | /** 1237 | * Returns a new Phaser.BitmapData object or an existing one from cache. 1238 | * If a new Phaser.BitmapData object is created, it is added to the cache. 1239 | * @param {number} width - The (new) width of the BitmapData object. 1240 | * @param {number} height - The (new) height of the BitmapData object. 1241 | * @param {string} key - The asset key for searching or adding it in the cache. 1242 | * @param {boolean} [even=true] - If true, warns if the dimensions are not even. 1243 | * @return {Phaser.BitmapData} - The (old or new) Phaser.BitmapData object. 1244 | */ 1245 | Supercold._BaseState.prototype.getBitmapData = function(width, height, key, even) { 1246 | var bmd; 1247 | 1248 | if (even === undefined) even = true; 1249 | 1250 | // NOTE: When bound to a Sprite, to avoid single-pixel jitters on mobile 1251 | // devices, it is strongly recommended to use Sprite sizes that are even 1252 | // on both axis, so warn if they are not! 1253 | // https://phaser.io/docs/2.6.2/Phaser.Physics.P2.Body.html 1254 | if (DEBUG && even && (width % 2 === 1 || height % 2 === 1)) { 1255 | warn('Sprite with odd dimension!'); 1256 | } 1257 | 1258 | // Resizing or creating from scratch? 1259 | if (this.cache.checkBitmapDataKey(key)) { 1260 | bmd = this.cache.getBitmapData(key); 1261 | // Due to the way we scale bitmaps, it is possible for the same width 1262 | // and height to be requested (especially when multiple resize events 1263 | // fire in rapid succesion), so the bitmap state will not be cleared! 1264 | // github.com/photonstorm/phaser/blob/v2.6.2/src/gameobjects/BitmapData.js#L552 1265 | // Set the width of the underlying canvas manually to clear its state. 1266 | if (bmd.width === width && bmd.height === height) { 1267 | bmd.canvas.width = width; 1268 | } else { 1269 | bmd.resize(width, height); 1270 | } 1271 | } else { 1272 | // 'add.bitmapData' does the same thing as 'make.bitmapData', 1273 | // i.e. it doesn't actually add the bitmapData object to the world. 1274 | // (BitmapData's are Game Objects and don't live on the display list.) 1275 | bmd = this.make.bitmapData(width, height, key, ADD_TO_CACHE); 1276 | } 1277 | return bmd; 1278 | }; 1279 | 1280 | /** 1281 | * Returns a new Phaser.BitmapData object or an existing one from cache. 1282 | * If a new Phaser.BitmapData object is created, it is added to the cache. 1283 | * @param {number} width - The (new) width of the BitmapData object. 1284 | * @param {number} height - The (new) height of the BitmapData object. 1285 | * @param {string} key - The asset key for searching or adding it in the cache. 1286 | * @return {Phaser.BitmapData} - The (old or new) Phaser.BitmapData object. 1287 | */ 1288 | Supercold._BaseState.prototype.getBitmapData2 = function(width, height, key) { 1289 | return this.getBitmapData(width, height, key, false); 1290 | }; 1291 | 1292 | 1293 | Supercold._BaseState.prototype._makeLiveEntityBitmap = function(style, key) { 1294 | var player = Supercold.player, width, bmd, ctx; 1295 | 1296 | function drawNozzle(ctx) { 1297 | // Note the fix values. 1298 | ctx.translate(-(style.lineWidth/2 + 0.13), -(style.lineWidth/2 + 0.12)); 1299 | ctx.beginPath(); 1300 | ctx.moveTo(0, player.radius); 1301 | ctx.lineTo(player.radius, player.radius); 1302 | ctx.lineTo(player.radius, 0); 1303 | } 1304 | 1305 | // Same width and height. 1306 | // Note the slack value to account for the shadow blur. 1307 | width = 2*player.radius + (2/3 * player.radius) + 1.5*style.shadowBlur; 1308 | width = even(width * this.world.scale.x); 1309 | bmd = this.getBitmapData(width, width, key); 1310 | ctx = bmd.ctx; 1311 | 1312 | ctx.fillStyle = style.color; 1313 | ctx.strokeStyle = style.strokeStyle; 1314 | ctx.lineWidth = style.lineWidth; 1315 | // shadowOffsetX/Y is NOT affected by the current transformation matrix! 1316 | ctx.shadowOffsetX = style.shadowOffsetX * this.world.scale.x; 1317 | ctx.shadowOffsetY = style.shadowOffsetY * this.world.scale.y; 1318 | // shadowBlur does NOT correspond to a number of pixels and 1319 | // is NOT affected by the current transformation matrix! 1320 | // Use world.scale as an approximation for the effect. 1321 | ctx.shadowBlur = style.shadowBlur * this.world.scale.x; 1322 | ctx.lineJoin = 'round'; 1323 | 1324 | // Start from the center. 1325 | ctx.translate(bmd.width / 2, bmd.height / 2); 1326 | // Rotate so that the entity will point to the right. 1327 | ctx.rotate(-Math.PI / 4); 1328 | ctx.scale(this.world.scale.x, this.world.scale.y); 1329 | 1330 | ctx.save(); 1331 | ctx.shadowColor = style.shadowColor; 1332 | ctx.save(); 1333 | // Draw the nozzle. 1334 | drawNozzle(ctx); 1335 | ctx.fill(); 1336 | // Draw the nozzle outline (twice to make the shadow stronger). 1337 | ctx.stroke(); 1338 | ctx.stroke(); 1339 | ctx.restore(); 1340 | // Draw the body (twice to make the shadow stronger). 1341 | circle(ctx, 0, 0, player.radius); 1342 | ctx.fill(); 1343 | ctx.fill(); 1344 | // Draw the outline a little smaller to account for the line width. 1345 | circle(ctx, 0, 0, player.radius - (style.lineWidth / 2)); 1346 | ctx.stroke(); 1347 | ctx.restore(); 1348 | // Draw the nozzle one last time to cover the shadow in the joint areas. 1349 | drawNozzle(ctx); 1350 | ctx.stroke(); 1351 | }; 1352 | 1353 | Supercold._BaseState.prototype._makePlayerBitmap = function() { 1354 | this._makeLiveEntityBitmap(Supercold.style.player, CACHE.KEY.PLAYER); 1355 | }; 1356 | 1357 | Supercold._BaseState.prototype._makeBotBitmap = function() { 1358 | this._makeLiveEntityBitmap(Supercold.style.bot, CACHE.KEY.BOT); 1359 | }; 1360 | 1361 | Supercold._BaseState.prototype._makeThrowableBitmap = function() { 1362 | var radius = Supercold.throwable.radius, 1363 | scale = this.world.scale, 1364 | width, bmd, ctx; 1365 | 1366 | width = even(2*radius * scale.x); 1367 | bmd = this.getBitmapData(width, width, CACHE.KEY.THROWABLE); 1368 | ctx = bmd.ctx; 1369 | 1370 | ctx.fillStyle = Supercold.style.throwable.color; 1371 | ctx.translate(bmd.width / 2, bmd.height / 2); 1372 | ctx.scale(scale.x, scale.y); 1373 | circle(ctx, 0, 0, radius); 1374 | ctx.fill(); 1375 | }; 1376 | 1377 | Supercold._BaseState.prototype._makeBulletBitmap = function() { 1378 | var scale = this.world.scale, 1379 | bullet = Supercold.bullet, 1380 | scaledWidth = bullet.width * scale.x, 1381 | scaledHeight = bullet.height * scale.y, 1382 | evenedWidth = even(scaledWidth), 1383 | evenedHeight = even(scaledHeight), 1384 | bmd = this.getBitmapData(evenedWidth, evenedHeight, CACHE.KEY.BULLET), 1385 | ctx = bmd.ctx; 1386 | 1387 | // In contrast to the other bitmaps, we don't keep the bullet perfectly 1388 | // proportional to the world scale. This helped with some centering issues. 1389 | 1390 | ctx.fillStyle = Supercold.style.bullet.color; 1391 | ctx.lineCap = ctx.lineJoin = 'round'; 1392 | 1393 | // Draw the body. 1394 | bmd.rect(0, 0, bullet.bodyLen * scale.x, evenedHeight); 1395 | // Draw the tip. 1396 | // For some reason, this needs to be shifted left by 1 pixel. 1397 | ctx.translate((bullet.bodyLen - 1) * scale.x, 0); 1398 | ctx.save(); 1399 | ctx.translate(0, evenedHeight / 2); 1400 | // Make the tip pointier. 1401 | ctx.scale(2, 1); 1402 | ctx.beginPath(); 1403 | ctx.arc(0, 0, evenedHeight / 2, 3 * Math.PI/2, Math.PI/2); 1404 | ctx.fill(); 1405 | ctx.restore(); 1406 | // Draw the mark. 1407 | ctx.fillStyle = Supercold.style.bullet.markColor; 1408 | bmd.rect(0, 0, 1 * scale.x, evenedHeight); 1409 | }; 1410 | 1411 | Supercold._BaseState.prototype._makeBulletTrailBitmap = function() { 1412 | var scale = this.world.scale, 1413 | style = Supercold.style.trail, 1414 | scaledWidth = style.x2 * scale.x, 1415 | scaledheight = Supercold.bullet.height * scale.y, 1416 | ceiledWidth = Math.ceil(scaledWidth), 1417 | ceiledHeight = Math.ceil(scaledheight), 1418 | bmd = this.getBitmapData2(ceiledWidth, ceiledHeight, CACHE.KEY.TRAIL), 1419 | ctx = bmd.ctx; 1420 | 1421 | // Shift the trail to account for rounding up and center it vertically. 1422 | ctx.translate((ceiledWidth - scaledWidth) / 2, ceiledHeight / 2); 1423 | 1424 | ctx.scale(scale.x, scale.y); 1425 | ctx.strokeStyle = createLinearGradient( 1426 | ctx, style.x1, style.y1, style.x2, style.y2, style.colorStops); 1427 | // Make it a little thinner. 2 space units seem best. 1428 | ctx.lineWidth = Supercold.bullet.height - 2; 1429 | ctx.lineCap = 'round'; 1430 | ctx.shadowOffsetX = style.shadowOffsetX * scale.x; 1431 | ctx.shadowOffsetY = style.shadowOffsetY * scale.y; 1432 | ctx.shadowBlur = style.shadowBlur * scale.x; 1433 | ctx.shadowColor = style.shadowColor; 1434 | 1435 | // Note the offsets on the x-axis, so as to leave space for the round caps. 1436 | line(ctx, style.x1 + 2, 0, style.x2 - 4, 0); 1437 | }; 1438 | 1439 | Supercold._BaseState.prototype._makeBackgroundBitmap = function() { 1440 | var scale = this.world.scale, 1441 | style = Supercold.style, cellDim = Supercold.cell.width, 1442 | // Even cell count for centering and +2 cells for tile scrolling. 1443 | width = (even(NATIVE_WIDTH / cellDim) + 2) * cellDim, 1444 | height = (even(NATIVE_HEIGHT / cellDim) + 2) * cellDim, 1445 | padWidth = Math.ceil(PADDING.width / cellDim) * cellDim, 1446 | padHeight = Math.ceil(PADDING.height / cellDim) * cellDim, 1447 | scaledPaddedWidth = Math.ceil((width + 2*padWidth) * scale.x), 1448 | scaledPaddedHeight = Math.ceil((height + 2*padHeight) * scale.y), 1449 | scaledCellWidth = Math.ceil(cellDim * scale.x), 1450 | scaledCellHeight = Math.ceil(cellDim * scale.y), 1451 | bmd, cellbmd; 1452 | 1453 | function drawCell(bgColor, color1, shadowColor1, color2, shadowColor2) { 1454 | var skewFactor = Math.tan(style.lines.angle), 1455 | cellDim = Supercold.cell.width, 1456 | ctx = cellbmd.ctx, i; 1457 | 1458 | // Clear everything from previous operations. 1459 | ctx.clearRect(0, 0, cellbmd.width, cellbmd.height); 1460 | ctx.save(); 1461 | // Draw background color. 1462 | ctx.fillStyle = bgColor; 1463 | ctx.fillRect(0, 0, cellbmd.width, cellbmd.height); 1464 | 1465 | // Draw blurred lines. (I know you want it. #joke -.-') 1466 | ctx.save(); 1467 | ctx.scale(scale.x, scale.y); 1468 | ctx.transform(1, 0, -skewFactor, 1, 0, 0); 1469 | ctx.lineWidth = style.lines.lineWidth; 1470 | ctx.strokeStyle = style.lines.color; 1471 | ctx.shadowOffsetX = style.lines.shadowOffsetX * scale.x; 1472 | ctx.shadowOffsetY = style.lines.shadowOffsetY * scale.y; 1473 | ctx.shadowBlur = style.lines.shadowBlur * scale.x; 1474 | ctx.shadowColor = style.lines.shadowColor; 1475 | ctx.beginPath(); 1476 | for (i = 0; i <= cellDim + (cellDim / skewFactor); i += 8) { 1477 | ctx.moveTo(i, 0); 1478 | ctx.lineTo(i, cellDim); 1479 | } 1480 | ctx.stroke(); 1481 | ctx.restore(); 1482 | 1483 | ctx.shadowOffsetX = style.grid.shadowOffsetX * scale.x; 1484 | ctx.shadowOffsetY = style.grid.shadowOffsetY * scale.y; 1485 | ctx.shadowBlur = style.grid.shadowBlur * scale.x; 1486 | 1487 | // Draw horizontal lines. 1488 | ctx.fillStyle = color1; 1489 | ctx.shadowColor = shadowColor1; 1490 | ctx.fillRect(0, 0, cellbmd.width, 1 * scale.y); 1491 | //ctx.fillRect(0, cellbmd.height, cellbmd.width, 1 * scale.y); 1492 | 1493 | // Draw vertical lines. 1494 | ctx.fillStyle = color2; 1495 | ctx.shadowColor = shadowColor2; 1496 | ctx.fillRect(0, 0, 1 * scale.x, cellbmd.height); 1497 | //ctx.fillRect(cellbmd.width, 0, 1 * scale.x, cellbmd.height); 1498 | ctx.restore(); 1499 | } 1500 | 1501 | function drawCells(x, y, width, height) { 1502 | var ctx = bmd.ctx; 1503 | 1504 | ctx.save(); 1505 | ctx.beginPath(); 1506 | ctx.fillStyle = ctx.createPattern(cellbmd.canvas, 'repeat'); 1507 | ctx.rect(x, y, width, height); 1508 | // Scale to account for ceiling. 1509 | ctx.scale((cellDim * scale.x) / scaledCellWidth, 1510 | (cellDim * scale.y) / scaledCellHeight); 1511 | ctx.fill(); 1512 | ctx.restore(); 1513 | } 1514 | 1515 | bmd = this.getBitmapData2(scaledPaddedWidth, scaledPaddedHeight, CACHE.KEY.BG); 1516 | cellbmd = this.make.bitmapData(scaledCellWidth, scaledCellHeight); 1517 | 1518 | // Disable image smoothing to keep the cells drawn with createPattern crisp! 1519 | // Unfortunately, this feature is not supported that well. It's something... 1520 | // developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled 1521 | // CAUTION: In certain resolutions, this causes some thin lines to disappear! 1522 | Phaser.Canvas.setSmoothingEnabled(bmd.ctx, false); 1523 | 1524 | // Draw dark cells everywhere. 1525 | drawCell(style.background.darkColor, 1526 | style.grid.colorOuter, style.grid.shadowColorOuter, 1527 | style.grid.colorOuter, style.grid.shadowColorOuter); 1528 | drawCells(0, 0, scaledPaddedWidth, scaledPaddedHeight); 1529 | 1530 | // Draw light cells over dark cells. 1531 | drawCell(style.background.lightColor, 1532 | style.grid.color1, style.grid.shadowColor1, 1533 | style.grid.color2, style.grid.shadowColor2); 1534 | // +/-1 for the red border 1535 | drawCells((padWidth + 1) * scale.x, (padHeight + 1) * scale.y, 1536 | (width - 1) * scale.x, (height - 1) * scale.y); 1537 | 1538 | // The cell bitmap is not needed anymore. 1539 | cellbmd.destroy(); 1540 | }; 1541 | 1542 | Supercold._BaseState.prototype._makeFlashBitmap = function() { 1543 | var width = Math.ceil(NATIVE_WIDTH * this.world.scale.x), 1544 | height = Math.ceil(NATIVE_HEIGHT * this.world.scale.y), 1545 | centerX = width / 2, centerY = height / 2, 1546 | radius = Math.min(centerX, centerY), 1547 | bmd = this.getBitmapData2(width, height, CACHE.KEY.FLASH), 1548 | ctx = bmd.ctx; 1549 | 1550 | // Create an oval flash. 1551 | ctx.translate(centerX, 0); 1552 | ctx.scale(2, 1); 1553 | ctx.translate(-centerX, 0); 1554 | 1555 | ctx.fillStyle = createRadialGradient( 1556 | ctx, centerX, centerY, 0, centerX, centerY, radius, 1557 | Supercold.style.flash.colorStops); 1558 | ctx.fillRect(0, 0, width, height); 1559 | }; 1560 | 1561 | Supercold._BaseState.prototype._makeOverlayBitmaps = function() { 1562 | var width = Math.ceil(NATIVE_WIDTH * this.world.scale.x), 1563 | height = Math.ceil(NATIVE_HEIGHT * this.world.scale.y), 1564 | bmd; 1565 | 1566 | bmd = this.getBitmapData2(width, height, CACHE.KEY.OVERLAY_DARK); 1567 | bmd.rect(0, 0, width, height, Supercold.style.overlay.darkColor); 1568 | bmd = this.getBitmapData2(width, height, CACHE.KEY.OVERLAY_LIGHT); 1569 | bmd.rect(0, 0, width, height, Supercold.style.overlay.lightColor); 1570 | }; 1571 | 1572 | Supercold._BaseState.prototype.makeBitmaps = function() { 1573 | this._makePlayerBitmap(); 1574 | this._makeBotBitmap(); 1575 | this._makeThrowableBitmap(); 1576 | this._makeBulletBitmap(); 1577 | this._makeBulletTrailBitmap(); 1578 | this._makeBackgroundBitmap(); 1579 | this._makeFlashBitmap(); 1580 | this._makeOverlayBitmaps(); 1581 | }; 1582 | 1583 | 1584 | Supercold._BaseState.prototype.addBackground = function() { 1585 | var background = this.add.image(0, 0, this.cache.getBitmapData(CACHE.KEY.BG)); 1586 | background.anchor.set(0.5); 1587 | background.scale.set(1 / this.world.scale.x, 1 / this.world.scale.y); 1588 | return background; 1589 | }; 1590 | 1591 | Supercold._BaseState.prototype.getHudFontSize = function(baseSize) { 1592 | return Math.max(14, Math.round(baseSize * this.camera.scale.x)); 1593 | }; 1594 | 1595 | /** 1596 | * Shows lots of debug info about various Phaser objects on the screen. 1597 | * @param {Phaser.Sprite} [sprite] - An optional sprite to show debug info for. 1598 | */ 1599 | Supercold._BaseState.prototype.showDebugInfo = function(sprite) { 1600 | if (sprite) { 1601 | this.game.debug.spriteBounds(sprite); 1602 | this.game.debug.spriteInfo(sprite, DEBUG_POSX, 32); 1603 | this.game.debug.spriteCoords(sprite, DEBUG_POSX, 122); 1604 | } 1605 | this.game.debug.cameraInfo(this.camera, DEBUG_POSX, 200); 1606 | this.game.debug.inputInfo(DEBUG_POSX, 280); 1607 | this.game.debug.pointer(this.input.activePointer); 1608 | this.game.debug.text('DEBUG: ' + DEBUG, DEBUG_POSX, this.game.height-16); 1609 | }; 1610 | 1611 | 1612 | /** 1613 | * Since our game is set to Scalemode 'RESIZE', this method will be called 1614 | * automatically to handle resizing. Since subclasses of the BaseState 1615 | * inherit this method, they are able to handle resizing, too. 1616 | * 1617 | * @param {number} width - the new width 1618 | * @param {number} height - the new height 1619 | * @override 1620 | */ 1621 | Supercold._BaseState.prototype.resize = function(width, height) { 1622 | Supercold._ScalableState.prototype.resize.call(this, width, height); 1623 | // Create new bitmaps for our new resolution. This will keep them sharp! 1624 | this.makeBitmaps(); 1625 | if (DEBUG) log('Resized bitmaps.'); 1626 | }; 1627 | 1628 | /** 1629 | * Used here for debugging purposes. 1630 | * Subclasses may override this to render more info. 1631 | */ 1632 | Supercold._BaseState.prototype.render = function() { 1633 | if (DEBUG) { 1634 | this.showDebugInfo(); 1635 | } 1636 | }; 1637 | 1638 | /******************************* Boot State *******************************/ 1639 | 1640 | /** 1641 | * State used to boot the game. 1642 | * This state is used to perform any operations that should only be 1643 | * executed once for the entire game (e.g. setting game options, etc.). 1644 | * @constructor 1645 | */ 1646 | Supercold.Boot = function(game) {}; 1647 | 1648 | Supercold.Boot.prototype.init = function() { 1649 | if (DEBUG) log('Initializing Boot state...'); 1650 | 1651 | // Our game does not need multi-touch support, 1652 | // so it is recommended to set this to 1. 1653 | this.input.maxPointers = 1; 1654 | // Call event.preventDefault for DOM mouse events. 1655 | // NOTE: For some reason, as of February 2017 this doesn't work for right 1656 | // clicks (contextmenu event) at least on Chrome, Opera, FF and IE for Windows. 1657 | // Chrome also demonstrates this even worse behaviour: 1658 | // https://github.com/photonstorm/phaser/issues/2286 1659 | this.input.mouse.capture = true; 1660 | 1661 | // Let the game continue when the tab loses focus. This prevents cheating. 1662 | this.stage.disableVisibilityChange = !DEBUG; 1663 | 1664 | this.stage.backgroundColor = Supercold.style.stage.backgroundColor; 1665 | 1666 | this.scale.scaleMode = Phaser.ScaleManager.RESIZE; 1667 | 1668 | // Let the camera view bounds be non-integer. This makes the motion smooth! 1669 | // https://phaser.io/docs/2.6.2/Phaser.Camera.html#roundPx 1670 | // https://phaser.io/docs/2.6.2/Phaser.Camera.html#follow 1671 | this.camera.roundPx = false; 1672 | 1673 | this.physics.startSystem(PHYSICS_SYSTEM); 1674 | this.physics.p2.setImpactEvents(true); 1675 | }; 1676 | 1677 | Supercold.Boot.prototype.create = function() { 1678 | if (DEBUG) log('Creating Boot state...'); 1679 | 1680 | this.state.start('Preloader'); 1681 | }; 1682 | 1683 | /***************************** Preloader State ****************************/ 1684 | 1685 | /** 1686 | * State used for loading or creating any assets needed in the game. 1687 | * @constructor 1688 | */ 1689 | Supercold.Preloader = function(game) { 1690 | Supercold._BaseState.call(this, game); 1691 | }; 1692 | 1693 | Supercold.Preloader.prototype = Object.create(Supercold._BaseState.prototype); 1694 | Supercold.Preloader.prototype.constructor = Supercold.Preloader; 1695 | 1696 | Supercold.Preloader.prototype.preload = function() { 1697 | if (DEBUG) { 1698 | this.load.onFileComplete.add(function(progress, key, success, totalLF, totalF) { 1699 | log(((success) ? 'Loaded' : 'Failed to load'), key, 'asset'); 1700 | log('Progress: ' + progress + ', Total loaded files: ' + totalLF + 1701 | ', Total files: ' + totalF); 1702 | }, this); 1703 | this.load.onLoadComplete.add(function() { 1704 | log('Asset loading completed'); 1705 | }, this); 1706 | } 1707 | this.load.audio('superhot', ['audio/superhot.mp3', 'audio/superhot.ogg']); 1708 | }; 1709 | 1710 | Supercold.Preloader.prototype.create = function() { 1711 | if (DEBUG) log('Creating Preloader state...'); 1712 | 1713 | // Scaling should be specified first. 1714 | this.setScaling(); 1715 | this.makeBitmaps(); 1716 | }; 1717 | 1718 | Supercold.Preloader.prototype.update = function() { 1719 | // No actual need to wait for asset loading. 1720 | this.state.start('MainMenu'); 1721 | }; 1722 | 1723 | /***************************** Main Menu State ****************************/ 1724 | 1725 | /** 1726 | * @constructor 1727 | */ 1728 | Supercold.MainMenu = function(game) { 1729 | Supercold._BaseState.call(this, game); 1730 | 1731 | this._announcer = null; 1732 | }; 1733 | 1734 | Supercold.MainMenu.prototype = Object.create(Supercold._BaseState.prototype); 1735 | Supercold.MainMenu.prototype.constructor = Supercold.MainMenu; 1736 | 1737 | Supercold.MainMenu.prototype.create = function() { 1738 | if (DEBUG) log('Creating MainMenu state...'); 1739 | 1740 | // Scaling should be specified first. 1741 | this.setScaling(); 1742 | 1743 | // Disable bounds checking for the camera, since it messes up centering. 1744 | this.camera.bounds = null; 1745 | // No need for the camera to focus on anything here! 1746 | 1747 | this._announcer = new Announcer(this.game, Supercold.texts.SUPERCOLD, { 1748 | initDelay: 750, 1749 | nextDelay: 1200, 1750 | flashTint: 0x151515, 1751 | repeat: true 1752 | }).announce(); 1753 | 1754 | this.game.supercold.onMainMenuOpen(); 1755 | // We will transition to the next state through the DOM menu! 1756 | }; 1757 | 1758 | Supercold.MainMenu.prototype.resize = function(width, height) { 1759 | Supercold._BaseState.prototype.resize.call(this, width, height); 1760 | this._announcer.resize(); 1761 | }; 1762 | 1763 | /******************************* Intro State ******************************/ 1764 | 1765 | /** 1766 | * @constructor 1767 | */ 1768 | Supercold.Intro = function(game) { 1769 | Supercold._BaseState.call(this, game); 1770 | 1771 | this._background = null; 1772 | this._announcer = null; 1773 | }; 1774 | 1775 | Supercold.Intro.prototype = Object.create(Supercold._BaseState.prototype); 1776 | Supercold.Intro.prototype.constructor = Supercold.Intro; 1777 | 1778 | Supercold.Intro.prototype.create = function() { 1779 | if (DEBUG) log('Creating Intro state...'); 1780 | 1781 | // Scaling should be specified first. 1782 | this.setScaling(); 1783 | 1784 | this._background = this.addBackground(); 1785 | 1786 | // Disable bounds checking for the camera, since it messes up centering. 1787 | this.camera.bounds = null; 1788 | this.camera.follow(this._background); 1789 | 1790 | this._announcer = new Announcer(this.game, Supercold.texts.SUPERCOLD, { 1791 | initDelay: 600, 1792 | nextDelay: 700, 1793 | overlay: true, 1794 | onComplete: (function startLevel() { 1795 | // Start at the last level that the player was in or the first one. 1796 | this.state.start('Game', CLEAR_WORLD, !CLEAR_CACHE, { 1797 | level: Supercold.storage.loadLevel() 1798 | }); 1799 | }).bind(this) 1800 | }).announce(); 1801 | }; 1802 | 1803 | Supercold.Intro.prototype.resize = function(width, height) { 1804 | Supercold._BaseState.prototype.resize.call(this, width, height); 1805 | this.rescale(this._background); 1806 | this._announcer.resize(); 1807 | }; 1808 | 1809 | /******************************* Game State *******************************/ 1810 | 1811 | var ANGLE_1DEG = 1 * Math.PI/180; // 1 degree 1812 | 1813 | /***** Sprites and Groups *****/ 1814 | 1815 | /* 1816 | * IMPORTANT: Don't enable physics in the sprite constructor, since this slows 1817 | * creation down significantly for some reason! Enable it in the group instead. 1818 | */ 1819 | 1820 | /** 1821 | * A generic sprite for our game. 1822 | * It provides useful methods for handling the Phaser World scaling. 1823 | * @constructor 1824 | */ 1825 | function Sprite(game, x, y, key, _frame) { 1826 | Phaser.Sprite.call(this, game, x, y, key, _frame); 1827 | this.name = 'Supercold Sprite'; 1828 | } 1829 | 1830 | Sprite.prototype = Object.create(Phaser.Sprite.prototype); 1831 | Sprite.prototype.constructor = Sprite; 1832 | 1833 | /** 1834 | * Moves the sprite forward, based on the given rotation and speed. 1835 | * The sprite must have a P2 body for this method to work correctly. 1836 | * (p2.body.moveForward does not work in our case, so I rolled my own.) 1837 | */ 1838 | Sprite.prototype.moveForward = function(rotation, speed) { 1839 | var velocity = this.body.velocity; 1840 | 1841 | // Note that Phaser.Physics.Arcade.html#velocityFromRotation won't work, 1842 | // due to the Phaser.Physics.P2.InversePointProxy body.velocity instance. 1843 | velocity.x = speed * Math.cos(rotation); 1844 | velocity.y = speed * Math.sin(rotation); 1845 | }; 1846 | 1847 | /** 1848 | * One of the game's live entities, i.e. the player or a bot. 1849 | * @constructor 1850 | * @abstract 1851 | */ 1852 | function LiveSprite(game, x, y, key, _frame) { 1853 | Sprite.call(this, game, x, y, key, _frame); 1854 | 1855 | this.name = 'LiveSprite'; 1856 | /** 1857 | * Time until the next bullet can be fired. 1858 | */ 1859 | this.remainingTime = 0; 1860 | /** 1861 | * A Weapon to shoot with. 1862 | */ 1863 | this.weapon = null; 1864 | 1865 | /** 1866 | * Tells if the sprite is currently dodging a bullet. 1867 | */ 1868 | this.dodging = false; 1869 | /** 1870 | * The direction in which it is dodging. 1871 | */ 1872 | this._direction = 0; 1873 | /** 1874 | * How long it will dodge. 1875 | */ 1876 | this._duration = 0; 1877 | } 1878 | 1879 | LiveSprite.prototype = Object.create(Sprite.prototype); 1880 | LiveSprite.prototype.constructor = LiveSprite; 1881 | 1882 | /** 1883 | * Resets the LiveSprite. 1884 | * LiveSprite's may be recycled in a group, so override this method in 1885 | * derived objects if necessary to reset any extra state they introduce. 1886 | */ 1887 | LiveSprite.prototype.reset = function(x, y, _health) { 1888 | Sprite.prototype.reset.call(this, x, y, _health); 1889 | this.remainingTime = 0; 1890 | this.weapon = null; 1891 | 1892 | this.dodging = false; 1893 | this._direction = 0; 1894 | this._duration = 0; 1895 | }; 1896 | 1897 | /** 1898 | * Fires the weapon that the entity holds. 1899 | */ 1900 | LiveSprite.prototype.fire = function() { 1901 | if (DEBUG) log('Bullet exists:', !!this.weapon.bullets.getFirstExists(!EXISTS)); 1902 | this.weapon.fire(this); 1903 | this.remainingTime = this.weapon.fireRate; 1904 | }; 1905 | 1906 | /** 1907 | * The player. 1908 | * @constructor 1909 | */ 1910 | function Player(game, x, y, scale) { 1911 | LiveSprite.call(this, game, x, y, game.cache.getBitmapData(CACHE.KEY.PLAYER)); 1912 | 1913 | this.name = 'Player'; 1914 | this.baseScale = scale || 1; 1915 | this.radius = Supercold.player.radius * this.baseScale; 1916 | this._dodgeRemainingTime = Player.DODGE_RELOAD_TIME; 1917 | 1918 | this.game.add.existing(this); 1919 | 1920 | // No group for Player, so enable physics here. 1921 | this.game.physics.enable(this, PHYSICS_SYSTEM, DEBUG); 1922 | this.body.setCircle(Supercold.player.radius * this.baseScale); 1923 | 1924 | // Account for world scaling. 1925 | this.resize(this.game.world.scale); 1926 | } 1927 | 1928 | Player.DODGE_RELOAD_TIME = 0.075; 1929 | 1930 | Player.prototype = Object.create(LiveSprite.prototype); 1931 | Player.prototype.constructor = Player; 1932 | 1933 | Player.prototype.kill = function() { 1934 | LiveSprite.prototype.kill.call(this); 1935 | // TODO: Add fancy kill effect. 1936 | }; 1937 | 1938 | /** 1939 | * Handles resizing of the player. 1940 | * Useful when the game world is rescaled. 1941 | * @param {Phaser.Point} worldScale - The scale of the world. 1942 | */ 1943 | Player.prototype.resize = function(worldScale) { 1944 | this.scale.set(this.baseScale / worldScale.x, this.baseScale / worldScale.y); 1945 | }; 1946 | 1947 | /** 1948 | * Rotates the player so as to point to the active pointer. 1949 | * @return {boolean} - true, if the rotation changed. 1950 | */ 1951 | Player.prototype.rotate = function() { 1952 | var playerRotated, newRotation; 1953 | 1954 | // Even though we use P2 physics, this function should work just fine. 1955 | // Calculate the angle using World coords, because scaling messes it up. 1956 | newRotation = this.game.physics.arcade.angleToPointer( 1957 | this, undefined, USE_WORLD_COORDS); 1958 | playerRotated = (this.body.rotation !== newRotation); 1959 | this.body.rotation = newRotation; 1960 | return playerRotated; 1961 | }; 1962 | 1963 | /** 1964 | * Makes the player dodge in the direction specified. 1965 | * @param {number} direction - The direction in which it will dodge. 1966 | */ 1967 | Player.prototype.dodge = function(direction) { 1968 | if (!this.dodging && this._dodgeRemainingTime <= 0) { 1969 | this.dodging = true; 1970 | this._duration = 0.075; // secs 1971 | this._direction = direction; 1972 | } 1973 | }; 1974 | 1975 | /** 1976 | * Advances the player, based on their state and the state of the game. 1977 | * NOTE: This doesn't check if the player is alive or not! 1978 | */ 1979 | Player.prototype.advance = function(moved, direction, elapsedTime) { 1980 | this.remainingTime -= elapsedTime; 1981 | this._dodgeRemainingTime -= elapsedTime; 1982 | 1983 | if (this.dodging) { 1984 | this.moveForward(this._direction, Supercold.speeds.dodge); 1985 | this._duration -= elapsedTime; 1986 | if (this._duration <= 0) { 1987 | this.dodging = false; 1988 | this._dodgeRemainingTime = Player.DODGE_RELOAD_TIME; 1989 | } 1990 | return; 1991 | } 1992 | 1993 | if (moved) { 1994 | this.moveForward(direction, Supercold.speeds.player); 1995 | } else { 1996 | this.body.setZeroVelocity(); 1997 | } 1998 | }; 1999 | 2000 | 2001 | /** 2002 | * Returns a function that produces the regular movement for the bot. 2003 | * @return {function} - The mover. 2004 | */ 2005 | function newForwardMover() { 2006 | return function moveForward(sprite, speed, _player) { 2007 | sprite.moveForward(sprite.body.rotation, speed); 2008 | }; 2009 | } 2010 | 2011 | /** 2012 | * Returns a function that makes the bot not get too close to the player. 2013 | * @param {number} distance - The distance that the bot will keep. 2014 | * @return {function} - The mover. 2015 | */ 2016 | function newDistantMover(distance) { 2017 | return function moveDistant(sprite, speed, player) { 2018 | if (sprite.game.physics.arcade.distanceBetween(sprite, player) < distance) { 2019 | speed = 0; 2020 | } 2021 | sprite.moveForward(sprite.body.rotation, speed); 2022 | }; 2023 | } 2024 | 2025 | /** 2026 | * Returns a function that always strafes the bot once it gets close to the player. 2027 | * @param {number} distance - The distance that the bot will keep. 2028 | * @param {number} direction - The direction in which it will strafe. 2029 | * @return {function} - The mover. 2030 | */ 2031 | function newStrafingMover(distance, direction) { 2032 | return function moveStrafing(sprite, speed, player) { 2033 | if (sprite.game.physics.arcade.distanceBetween(sprite, player) < distance) { 2034 | sprite.moveForward(sprite.body.rotation + direction*0.8, speed); 2035 | } else { 2036 | sprite.moveForward(sprite.body.rotation, speed); 2037 | } 2038 | }; 2039 | } 2040 | 2041 | /** 2042 | * Returns a function that always strafes the bot once it gets close 2043 | * to the player, but also doesn't let it get too close to them. 2044 | * @param {number} distance - The distance that the bot will keep. 2045 | * @param {number} direction - The direction in which it will strafe. 2046 | * @return {function} - The mover. 2047 | */ 2048 | function newStrafingDistantMover(distance, direction) { 2049 | return function moveStrafingDistant(sprite, speed, player) { 2050 | if (sprite.game.physics.arcade.distanceBetween(sprite, player) < distance) { 2051 | sprite.moveForward(sprite.body.rotation + direction, speed); 2052 | } else { 2053 | sprite.moveForward(sprite.body.rotation, speed); 2054 | } 2055 | }; 2056 | } 2057 | 2058 | 2059 | /** 2060 | * A bot. 2061 | * @constructor 2062 | */ 2063 | function Bot(game, x, y, _key, _frame) { 2064 | LiveSprite.call(this, game, x, y, game.cache.getBitmapData(CACHE.KEY.BOT)); 2065 | 2066 | this.name = 'Bot ' + Bot.count++; 2067 | 2068 | // Don't fire immediately! 2069 | this.remainingTime = Supercold.initFireDelay; 2070 | 2071 | this.radius = -1; 2072 | 2073 | /** 2074 | * A function that encapsulates the movement logic of the bot. 2075 | */ 2076 | this.move = newForwardMover(); 2077 | } 2078 | 2079 | Bot.count = 0; 2080 | 2081 | Bot.VIEW_ANGLE = Math.PI / 2.25; 2082 | 2083 | Bot.prototype = Object.create(LiveSprite.prototype); 2084 | Bot.prototype.constructor = Bot; 2085 | 2086 | Bot.prototype.kill = function() { 2087 | LiveSprite.prototype.kill.call(this); 2088 | // TODO: Add fancy kill effect. 2089 | }; 2090 | 2091 | /** 2092 | * Resets the Bot. 2093 | * Bot's are recycled in their group, so we override 2094 | * this method to reset the extra state they introduce. 2095 | */ 2096 | Bot.prototype.reset = function(x, y, _health) { 2097 | LiveSprite.prototype.reset.call(this, x, y, _health); 2098 | this.remainingTime = Supercold.initFireDelay; 2099 | }; 2100 | 2101 | /** 2102 | * Fires the weapon that the entity holds. 2103 | */ 2104 | Bot.prototype.fire = function() { 2105 | var body = this.body, rotation = body.rotation, angle = 2 * ANGLE_1DEG; 2106 | 2107 | // Introduce some randomness in the aim. 2108 | body.rotation = rotation + this.game.rnd.realInRange(-angle, angle); 2109 | LiveSprite.prototype.fire.call(this); 2110 | body.rotation = rotation; 2111 | }; 2112 | 2113 | Bot.prototype._fire = function(level) { 2114 | var game = this.game; 2115 | 2116 | // Wait sometimes before firing to make things a bit more unpredictable. 2117 | if (game.rnd.frac() <= 1/5) { 2118 | this.remainingTime = game.rnd.between( 2119 | 0, Math.max(this.weapon.fireRate * (1 - level/100), 0)); 2120 | return; 2121 | } 2122 | this.fire(); 2123 | }; 2124 | 2125 | Bot.prototype._dodge = function(durationFix) { 2126 | var game = this.game; 2127 | 2128 | this.dodging = true; 2129 | this._duration = 0.25 + durationFix; // secs 2130 | this._direction = ((game.rnd.between(0, 1)) ? 1 : -1) * Math.PI/2; 2131 | }; 2132 | 2133 | /** 2134 | * Advances the bot, based on its state and the state of the game. 2135 | * Basically, this is the AI for the bot. 2136 | */ 2137 | Bot.prototype.advance = function(elapsedTime, speed, level, player, playerFired) { 2138 | var game = this.game, angleDiff, slowFactor; 2139 | 2140 | this.remainingTime -= elapsedTime; 2141 | 2142 | // Even though we use P2 physics, this function should work just fine. 2143 | this.body.rotation = game.physics.arcade.angleBetween(this, player); 2144 | // this.body.rotation = Math.atan2(player.y - this.y, player.x - this.x); 2145 | 2146 | // Only try to shoot if the bot is somewhat close to the player 2147 | if (game.physics.arcade.distanceBetween(this, player) < 2148 | (NATIVE_WIDTH + NATIVE_HEIGHT) / 2) { 2149 | // and if it is ready to fire. 2150 | if (this.remainingTime <= 0) { 2151 | this._fire(level); 2152 | } 2153 | } 2154 | 2155 | if (this.dodging) { 2156 | this.moveForward(this.body.rotation + this._direction, speed); 2157 | this._duration -= elapsedTime; 2158 | if (this._duration <= 0) { 2159 | this.dodging = false; 2160 | } 2161 | return; 2162 | } 2163 | 2164 | this.move(this, speed, player); 2165 | 2166 | // Dodge sometimes (chance: 1 / (60fps * x sec * slowFactor)). 2167 | slowFactor = game.time.physicsElapsed / elapsedTime; 2168 | if (game.rnd.frac() <= 1 / (game.time.desiredFps * 1.2 * slowFactor)) { 2169 | this._dodge(0.004 * level); 2170 | } 2171 | 2172 | // If the player fired and we are not already dodging, try to dodge. 2173 | if (playerFired) { 2174 | // Dodge only when the player is facing us, so it doesn't look stupid. 2175 | angleDiff = game.math.reverseAngle(player.body.rotation) - 2176 | game.math.normalizeAngle(this.body.rotation); 2177 | if (angleDiff < Bot.VIEW_ANGLE && angleDiff > -Bot.VIEW_ANGLE) { 2178 | // Dodge sometimes (chance in range [1/4,3/4]). 2179 | if (game.rnd.frac() <= 1/4 + Math.min(2/4, 2/4 * level/100)) { 2180 | this._dodge(0.005 * level); 2181 | } 2182 | } 2183 | } 2184 | }; 2185 | 2186 | 2187 | /** 2188 | * A weapon. Abstract base class. 2189 | * @param {BulletGroup} bullets - A group of bullets. 2190 | * @param {number} fireFactor - A factor for the firing rate. 2191 | * @constructor 2192 | * @abstract 2193 | */ 2194 | function Weapon(bullets, fireFactor) { 2195 | this.bullets = bullets; 2196 | this.fireRate = Supercold.baseFireRate * fireFactor; 2197 | } 2198 | 2199 | /** 2200 | * Fires bullets. 2201 | * @param {LiveSprite} entity - The entity that holds the weapon. 2202 | * @override 2203 | */ 2204 | Weapon.prototype.fire = function(entity) { 2205 | throw new Error('Abstract base class'); 2206 | }; 2207 | 2208 | /** 2209 | * A pistol. Fires one bullet at a time. The simplest gun. 2210 | * @param {BulletGroup} bullets - A group of bullets. 2211 | * @param {number} fireFactor - A factor for the firing rate. 2212 | * @constructor 2213 | */ 2214 | function Pistol(bullets, fireFactor) { 2215 | Weapon.call(this, bullets, fireFactor); 2216 | } 2217 | 2218 | Pistol.prototype = Object.create(Weapon.prototype); 2219 | Pistol.prototype.constructor = Pistol; 2220 | 2221 | /** 2222 | * Fires a bullet. 2223 | * @param {LiveSprite} entity - The entity that holds the weapon. 2224 | */ 2225 | Pistol.prototype.fire = function(entity) { 2226 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2227 | .fire(entity, entity.body.rotation); 2228 | }; 2229 | 2230 | /** 2231 | * A gun that fires two bullets at once. Accurate! 2232 | * @param {BulletGroup} bullets - A group of bullets. 2233 | * @param {number} fireFactor - A factor for the firing rate. 2234 | * @constructor 2235 | */ 2236 | function Burst(bullets, fireFactor) { 2237 | Weapon.call(this, bullets, fireFactor); 2238 | } 2239 | 2240 | Burst.prototype = Object.create(Weapon.prototype); 2241 | Burst.prototype.constructor = Burst; 2242 | 2243 | Burst.OFFSET = 16; // Space units 2244 | 2245 | /** 2246 | * Fires an array of bullets. 2247 | * @param {LiveSprite} entity - The entity that holds the weapon. 2248 | */ 2249 | Burst.prototype.fire = function(entity) { 2250 | var x = entity.x, y = entity.y, rotation = entity.body.rotation, 2251 | offsetX = Burst.OFFSET * Math.cos(rotation + Math.PI/2) / 2, 2252 | offsetY = Burst.OFFSET * Math.sin(rotation + Math.PI/2) / 2; 2253 | 2254 | // Move the entity to adjust the position of the bullet to be fired. 2255 | // Upper 2256 | entity.x = x + offsetX; 2257 | entity.y = y + offsetY; 2258 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2259 | // Lower 2260 | entity.x = x - offsetX; 2261 | entity.y = y - offsetY; 2262 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2263 | // Put it back in place. 2264 | entity.x = x; 2265 | entity.y = y; 2266 | }; 2267 | 2268 | /** 2269 | * A gun that fires three bullets at once. Accurate! 2270 | * @param {BulletGroup} bullets - A group of bullets. 2271 | * @param {number} fireFactor - A factor for the firing rate. 2272 | * @constructor 2273 | */ 2274 | function Burst3(bullets, fireFactor) { 2275 | Weapon.call(this, bullets, fireFactor); 2276 | 2277 | this.fireRate *= 1.111; 2278 | } 2279 | 2280 | Burst3.prototype = Object.create(Weapon.prototype); 2281 | Burst3.prototype.constructor = Burst3; 2282 | 2283 | Burst3.OFFSET = 16; 2284 | 2285 | /** 2286 | * Fires an array of bullets. 2287 | * @param {LiveSprite} entity - The entity that holds the weapon. 2288 | */ 2289 | Burst3.prototype.fire = function(entity) { 2290 | var x = entity.x, y = entity.y, rotation = entity.body.rotation, 2291 | offsetX = Burst3.OFFSET * Math.cos(rotation + Math.PI/2), 2292 | offsetY = Burst3.OFFSET * Math.sin(rotation + Math.PI/2); 2293 | 2294 | // Move the entity to adjust the position of the bullet to be fired. 2295 | // Upper 2296 | entity.x = x + offsetX; 2297 | entity.y = y + offsetY; 2298 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2299 | // Lower 2300 | entity.x = x - offsetX; 2301 | entity.y = y - offsetY; 2302 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2303 | // Middle 2304 | entity.x = x; 2305 | entity.y = y; 2306 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2307 | }; 2308 | 2309 | /** 2310 | * A blunderbuss. Fires many bullets at once, but inaccurately. 2311 | * @param {Phaser.Game} game - A reference to the currently running Game. 2312 | * @param {BulletGroup} bullets - A group of bullets. 2313 | * @param {number} fireFactor - A factor for the firing rate. 2314 | * @constructor 2315 | */ 2316 | function Blunderbuss(game, bullets, fireFactor) { 2317 | Weapon.call(this, bullets, fireFactor); 2318 | this.game = game; 2319 | 2320 | this.fireRate *= 1.2; 2321 | } 2322 | 2323 | Blunderbuss.prototype = Object.create(Weapon.prototype); 2324 | Blunderbuss.prototype.constructor = Blunderbuss; 2325 | 2326 | Blunderbuss.ANGLE = 13 * ANGLE_1DEG; 2327 | Blunderbuss.ANGLE_DIFF = 2 * ANGLE_1DEG; 2328 | Blunderbuss.OFFSET = 5; 2329 | 2330 | /** 2331 | * Fires multiple bullets. 2332 | * @param {LiveSprite} entity - The entity that holds the weapon. 2333 | */ 2334 | Blunderbuss.prototype.fire = function(entity) { 2335 | var angle = Blunderbuss.ANGLE, diff = Blunderbuss.ANGLE_DIFF, 2336 | x = entity.x, y = entity.y, rotation = entity.body.rotation, 2337 | offsetX = Blunderbuss.OFFSET * Math.cos(rotation + Math.PI/2), 2338 | offsetY = Blunderbuss.OFFSET * Math.sin(rotation + Math.PI/2), 2339 | factor = -3/2, rnd = this.game.rnd, i; 2340 | 2341 | // Move the entity to adjust the position of the bullet to be fired. 2342 | entity.x = x + factor*offsetX; 2343 | entity.y = y + factor*offsetY; 2344 | for (i = 1; i <= 4; ++i) { 2345 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2346 | .fire(entity, rotation + factor*angle + rnd.realInRange(-diff, diff)); 2347 | entity.x += offsetX; 2348 | entity.y += offsetY; 2349 | ++factor; 2350 | } 2351 | // Put it back in place. 2352 | entity.x = x; 2353 | entity.y = y; 2354 | }; 2355 | 2356 | /** 2357 | * A shotgun. Fires many bullets at once, but a somewhat inaccurately. 2358 | * @param {Phaser.Game} game - A reference to the currently running Game. 2359 | * @param {BulletGroup} bullets - A group of bullets. 2360 | * @param {number} fireFactor - A factor for the firing rate. 2361 | * @constructor 2362 | */ 2363 | function Shotgun(game, bullets, fireFactor) { 2364 | Weapon.call(this, bullets, fireFactor); 2365 | this.game = game; 2366 | 2367 | this.fireRate *= 1.111; 2368 | } 2369 | 2370 | Shotgun.prototype = Object.create(Weapon.prototype); 2371 | Shotgun.prototype.constructor = Shotgun; 2372 | 2373 | Shotgun.ANGLE = 20 * ANGLE_1DEG; 2374 | Shotgun.ANGLE_DIFF = 2 * ANGLE_1DEG; 2375 | 2376 | /** 2377 | * Fires multiple bullets. 2378 | * @param {LiveSprite} entity - The entity that holds the weapon. 2379 | */ 2380 | Shotgun.prototype.fire = function(entity) { 2381 | var angle = Shotgun.ANGLE, diff = Shotgun.ANGLE_DIFF, 2382 | rotation = entity.body.rotation, rnd = this.game.rnd; 2383 | 2384 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2385 | .fire(entity, rotation - angle + rnd.realInRange(-3*diff, diff)); 2386 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2387 | .fire(entity, rotation + rnd.realInRange(-diff, diff)); 2388 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2389 | .fire(entity, rotation + angle + rnd.realInRange(-diff, 3*diff)); 2390 | }; 2391 | 2392 | /** 2393 | * Double-barreled shotgun. Fires even more bullets at once more accurately. 2394 | * @param {Phaser.Game} game - A reference to the currently running Game. 2395 | * @param {BulletGroup} bullets - A group of bullets. 2396 | * @param {number} fireFactor - A factor for the firing rate. 2397 | * @constructor 2398 | */ 2399 | function DbShotgun(game, bullets, fireFactor) { 2400 | Weapon.call(this, bullets, fireFactor); 2401 | this.game = game; 2402 | } 2403 | 2404 | DbShotgun.ANGLE = 10 * ANGLE_1DEG; 2405 | DbShotgun.ANGLE_DIFF = 2 * ANGLE_1DEG; 2406 | DbShotgun.OFFSET = 6; 2407 | 2408 | DbShotgun.prototype = Object.create(Weapon.prototype); 2409 | DbShotgun.prototype.constructor = DbShotgun; 2410 | 2411 | /** 2412 | * Fires multiple bullets. 2413 | * @param {LiveSprite} entity - The entity that holds the weapon. 2414 | */ 2415 | DbShotgun.prototype.fire = function(entity) { 2416 | var angle = DbShotgun.ANGLE, diff = DbShotgun.ANGLE_DIFF, 2417 | x = entity.x, y = entity.y, rotation = entity.body.rotation, 2418 | offsetX = DbShotgun.OFFSET * Math.cos(rotation + Math.PI/2), 2419 | offsetY = DbShotgun.OFFSET * Math.sin(rotation + Math.PI/2), 2420 | rnd = this.game.rnd, i; 2421 | 2422 | for (i = -2; i <= 2; ++i) { 2423 | // Move the entity to adjust the position of the bullet to be fired. 2424 | entity.x = x + i*offsetX; 2425 | entity.y = y + i*offsetY; 2426 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL) 2427 | .fire(entity, rotation + i*angle + rnd.realInRange(-diff, diff)); 2428 | } 2429 | // Put it back in place. 2430 | entity.x = x; 2431 | entity.y = y; 2432 | }; 2433 | 2434 | /** 2435 | * A rifle. Fires bullets fast, but a little inaccurately. 2436 | * @param {Phaser.Game} game - A reference to the currently running Game. 2437 | * @param {BulletGroup} bullets - A group of bullets. 2438 | * @param {number} fireFactor - A factor for the firing rate. 2439 | * @constructor 2440 | */ 2441 | function Rifle(game, bullets, fireFactor) { 2442 | Weapon.call(this, bullets, 1); 2443 | this.game = game; 2444 | this.fireFactor = fireFactor; 2445 | 2446 | this._bulletCount = Rifle.BULLET_COUNT; 2447 | 2448 | this.fireRate = Rifle.FIRE_RATE * this.fireFactor; 2449 | } 2450 | 2451 | Rifle.BULLET_COUNT = 10; 2452 | Rifle.FIRE_RATE = Supercold.baseFireRate / 4; 2453 | Rifle.OFFSET = 16; 2454 | 2455 | Rifle.prototype = Object.create(Weapon.prototype); 2456 | Rifle.prototype.constructor = Rifle; 2457 | 2458 | /** 2459 | * Fires a bullet. 2460 | * @param {LiveSprite} entity - The entity that holds the weapon. 2461 | */ 2462 | Rifle.prototype.fire = function(entity) { 2463 | var rnd = this.game.rnd, rotation = entity.body.rotation, 2464 | offset = rnd.between(-Rifle.OFFSET, Rifle.OFFSET), 2465 | offsetX = offset * Math.cos(rotation + Math.PI/2), 2466 | offsetY = offset * Math.sin(rotation + Math.PI/2); 2467 | 2468 | // Move the entity to adjust the position of the bullet to be fired. 2469 | entity.x += offsetX; 2470 | entity.y += offsetY; 2471 | this.bullets.getFirstExists(!EXISTS, CREATE_IF_NULL).fire(entity, rotation); 2472 | // Put it back in place. 2473 | entity.x -= offsetX; 2474 | entity.y -= offsetY; 2475 | // Rifles have a variable fire rate. 2476 | if (--this._bulletCount > 0) { 2477 | this.fireRate = Rifle.FIRE_RATE * this.fireFactor * rnd.realInRange(0.9, 1.4); 2478 | } else { 2479 | this._bulletCount = Rifle.BULLET_COUNT; 2480 | this.fireRate = Rifle.FIRE_RATE * this.fireFactor * 3.5; 2481 | } 2482 | }; 2483 | 2484 | 2485 | /** 2486 | * A bullet fired from a Weapon. 2487 | * @constructor 2488 | */ 2489 | function Bullet(game, x, y, _key, _frame) { 2490 | Sprite.call(this, game, x, y, game.cache.getBitmapData(CACHE.KEY.BULLET)); 2491 | 2492 | this.name = 'Bullet ' + Bullet.count++; 2493 | /** 2494 | * Who shot the bullet. 2495 | */ 2496 | this.owner = null; 2497 | 2498 | // Using these properties does NOT work, because when checking the world 2499 | // bounds in the preUpdate method, the world scale is not taken into account! 2500 | // THIS TOOK ME A LONG TIME TO FIND OUT. -.-' 2501 | //this.checkWorldBounds = true; 2502 | //this.outOfBoundsKill = true; 2503 | } 2504 | 2505 | Bullet.count = 0; 2506 | 2507 | Bullet.prototype = Object.create(Sprite.prototype); 2508 | Bullet.prototype.constructor = Bullet; 2509 | 2510 | Bullet.prototype.kill = function() { 2511 | Sprite.prototype.kill.call(this); 2512 | // TODO: Add fancy kill effect. 2513 | }; 2514 | 2515 | /** 2516 | * Resets the Bullet. 2517 | * Bullet's are recycled in their group, so we override 2518 | * this method to reset the extra state they introduce. 2519 | */ 2520 | Bullet.prototype.reset = function(x, y, _health) { 2521 | Sprite.prototype.reset.call(this, x, y, _health); 2522 | this.owner = null; 2523 | }; 2524 | 2525 | /** 2526 | * Cached variable for the scaled world bounds. 2527 | */ 2528 | Bullet._worldBounds = new Phaser.Rectangle(); 2529 | 2530 | /** 2531 | * Overriden preUpdate method to handle world scaling and outOfBounds correctly. 2532 | * TODO: Find a better way to do this! 2533 | */ 2534 | Bullet.prototype.preUpdate = function() { 2535 | var camera = this.game.camera, scale = camera.scale; 2536 | 2537 | // 2538 | if (!Sprite.prototype.preUpdate.call(this) || !this.alive) { 2539 | return false; 2540 | } 2541 | 2542 | // Scale the world bounds. 2543 | Bullet._worldBounds.copyFrom(this.game.world.bounds); 2544 | Bullet._worldBounds.x *= scale.x; 2545 | Bullet._worldBounds.y *= scale.y; 2546 | Bullet._worldBounds.x -= camera.view.x; 2547 | Bullet._worldBounds.y -= camera.view.y; 2548 | Bullet._worldBounds.width *= scale.x; 2549 | Bullet._worldBounds.height *= scale.y; 2550 | if (!Bullet._worldBounds.intersects(this.getBounds())) { 2551 | this.kill(); 2552 | return false; 2553 | } 2554 | return true; 2555 | }; 2556 | 2557 | /** 2558 | * Moves the bullet forward, based on the given speed. 2559 | */ 2560 | Bullet.prototype.move = function(speed) { 2561 | Sprite.prototype.moveForward.call(this, this.body.rotation, speed); 2562 | }; 2563 | 2564 | /** 2565 | * Positions the bullet in front of the entity specified. 2566 | * @param {LiveSprite} entity - The entity that fired the bullet. 2567 | * @param {number} rotation - The rotation of the bullet (in radians). 2568 | */ 2569 | Bullet.prototype.fire = function(entity, rotation) { 2570 | // Place the bullet in front of the sprite, so that they don't collide! 2571 | var offset = entity.radius + Math.round(3/4 * Supercold.bullet.width); 2572 | 2573 | this.reset( 2574 | entity.x + offset*Math.cos(rotation), 2575 | entity.y + offset*Math.sin(rotation)); 2576 | this.owner = entity; 2577 | this.body.rotation = rotation; 2578 | // Dispatch the onRevived signal after setting rotation. 2579 | this.revive(); 2580 | }; 2581 | 2582 | /** 2583 | * The trail that a bullet leaves behind. 2584 | * @constructor 2585 | */ 2586 | function BulletTrail(game, x, y, _key, _frame) { 2587 | Phaser.Image.call(this, game, x, y, game.cache.getBitmapData(CACHE.KEY.TRAIL)); 2588 | 2589 | this.name = 'Trail ' + BulletTrail.count++; 2590 | this.anchor.x = 1; 2591 | this.anchor.y = 0.5; 2592 | 2593 | /** 2594 | * The bullet that this trail belongs to. 2595 | * Will be null if the bullet was destroyed. 2596 | */ 2597 | this.bullet = null; 2598 | /** 2599 | * Reference point for this trail (where it starts or stops). 2600 | */ 2601 | this.refX = 0; 2602 | this.refY = 0; 2603 | } 2604 | 2605 | BulletTrail.count = 0; 2606 | 2607 | BulletTrail.prototype = Object.create(Phaser.Image.prototype); 2608 | BulletTrail.prototype.constructor = BulletTrail; 2609 | 2610 | /** 2611 | * Resets the BulletTrail. 2612 | * BulletTrail's are recycled in their group, so we override 2613 | * this method to reset the extra state they introduce. 2614 | */ 2615 | BulletTrail.prototype.reset = function(x, y, _health) { 2616 | Phaser.Image.prototype.reset.call(this, x, y, _health); 2617 | this.refX = 0; 2618 | this.refY = 0; 2619 | }; 2620 | 2621 | /** 2622 | * Starts the trail. 2623 | * The trail starts moving and expanding behind the given bullet. 2624 | * @param {Bullet} bullet - The bullet for this trail. 2625 | */ 2626 | BulletTrail.prototype.start = function(bullet) { 2627 | this.rotation = bullet.body.rotation; 2628 | this.bullet = bullet; 2629 | this.refX = bullet.x; 2630 | this.refY = bullet.y; 2631 | this.width = 0; 2632 | }; 2633 | 2634 | /** 2635 | * Stops the trail. 2636 | * The trail stops moving and expanding, and becomes stray. 2637 | */ 2638 | BulletTrail.prototype.stop = function() { 2639 | this.refX = this.bullet.x; 2640 | this.refY = this.bullet.y; 2641 | this.bullet = null; 2642 | }; 2643 | 2644 | 2645 | /** 2646 | * A generic group for our game. 2647 | * It provides useful methods for handling the Phaser world scale. 2648 | * @param {Phaser.Game} game - A reference to the currently running Game. 2649 | * @param {number} [scale=1] - The base scale for this group. 2650 | * @param {string} [name='group'] - A name for this group. 2651 | * @constructor 2652 | */ 2653 | function Group(game, scale, name, _parent) { 2654 | Phaser.Group.call(this, game, _parent, name, false, true, PHYSICS_SYSTEM); 2655 | 2656 | /** 2657 | * The base scaling values that are used to calculate the final ones. 2658 | * Useful for storing scaling values to implement mutators. 2659 | */ 2660 | this.baseScale = scale || 1; 2661 | /** 2662 | * The actual scaling values that are applied to the sprites of the group. 2663 | * This takes the world scale into account. 2664 | */ 2665 | this.finalScale = new Phaser.Point( 2666 | this.baseScale / game.world.scale.x, this.baseScale / game.world.scale.y); 2667 | } 2668 | 2669 | Group.prototype = Object.create(Phaser.Group.prototype); 2670 | Group.prototype.constructor = Group; 2671 | 2672 | /** 2673 | * Appends info about this group to the name of the child specified. 2674 | */ 2675 | Group.prototype.extendChildName = function(child) { 2676 | child.name += ', ' + this.getChildIndex(child) + ' in ' + this.name; 2677 | }; 2678 | 2679 | /** 2680 | * Sets the scaling values for all the children of the group. 2681 | * @param {Phaser.Point} worldScale - The scale of the world. 2682 | */ 2683 | Group.prototype.resize = function(worldScale) { 2684 | var length = this.children.length, i; 2685 | 2686 | this.finalScale.set( 2687 | this.baseScale / worldScale.x, this.baseScale / worldScale.y); 2688 | for (i = 0; i < length; ++i) { 2689 | this.children[i].scale.copyFrom(this.finalScale); 2690 | } 2691 | }; 2692 | 2693 | /** 2694 | * A group of 'Bot's with enabled physics. 2695 | * @param {Phaser.Game} game - A reference to the currently running Game. 2696 | * @param {number} [scale=1] - The base scale for this group. 2697 | * @constructor 2698 | */ 2699 | function BotGroup(game, scale) { 2700 | Group.call(this, game, scale, 'Bots'); 2701 | 2702 | this.classType = Bot; 2703 | 2704 | // Bots receive input (for hotswitching). 2705 | this.inputEnableChildren = true; 2706 | } 2707 | 2708 | BotGroup.prototype = Object.create(Group.prototype); 2709 | BotGroup.prototype.constructor = BotGroup; 2710 | 2711 | /** 2712 | * Creates a new Bot object and adds it to the top of this group. 2713 | */ 2714 | BotGroup.prototype.create = function(x, y, _key, _frame) { 2715 | var bot = Group.prototype.create.call(this, x, y, null, null); 2716 | 2717 | if (DEBUG) this.extendChildName(bot); 2718 | bot.radius = Supercold.player.radius * this.baseScale; 2719 | bot.scale.copyFrom(this.finalScale); 2720 | bot.body.setCircle(bot.radius); 2721 | bot.body.debug = DEBUG; 2722 | 2723 | // Players may click near the bot to shoot. 2724 | bot.input.pixelPerfectClick = true; 2725 | // Used for accurately using the hand cursor. 2726 | bot.input.pixelPerfectOver = true; 2727 | bot.input.useHandCursor = true; 2728 | 2729 | return bot; 2730 | }; 2731 | 2732 | /** 2733 | * Advances all alive bots in this group. 2734 | */ 2735 | BotGroup.prototype.advance = function( 2736 | elapsedTime, speed, level, player, playerFired) { 2737 | var length = this.children.length, bot, i; 2738 | 2739 | for (i = 0; i < length; ++i) { 2740 | bot = this.children[i]; 2741 | if (bot.alive) { 2742 | bot.advance(elapsedTime, speed, level, player, playerFired); 2743 | } 2744 | } 2745 | }; 2746 | 2747 | /** 2748 | * A group of 'Bullet's with enabled physics. 2749 | * @param {Phaser.Game} game - A reference to the currently running Game. 2750 | * @param {number} [scale=1] - The base scale for this group. 2751 | * @param {function(Bullet)} customizer - A function that customizes a bullet. 2752 | * @constructor 2753 | */ 2754 | function BulletGroup(game, scale, customizer, name) { 2755 | Group.call(this, game, scale, name || 'Bullets'); 2756 | this.classType = Bullet; 2757 | this.customizer = customizer; 2758 | } 2759 | 2760 | BulletGroup.prototype = Object.create(Group.prototype); 2761 | BulletGroup.prototype.constructor = BulletGroup; 2762 | 2763 | /** 2764 | * Creates a new Bullet object and adds it to the top of this group. 2765 | * All arguments given to this method are ignored. 2766 | */ 2767 | BulletGroup.prototype.create = function(_x, _y, _key, _frame) { 2768 | var bullet = Group.prototype.create.call(this, 0, 0, null, null, false), 2769 | specs = Supercold.bullet, 2770 | offsetX = (specs.width - specs.bodyLen) / 2; 2771 | 2772 | if (DEBUG) this.extendChildName(bullet); 2773 | bullet.scale.copyFrom(this.finalScale); 2774 | // Set two shapes to closely resemble the shape of the bullet. 2775 | bullet.body.setRectangle(specs.bodyLen, specs.height, -offsetX); 2776 | bullet.body.addCircle(specs.bodyLen / 2, offsetX); 2777 | // Do NOT produce contact forces, so that bullets do not 2778 | // change direction when colliding with other sprites. 2779 | // We need to access the P2JS internals for this. 2780 | // Note that making the bodies kinematic would not be enough, 2781 | // since they would still produce collisions with the sprites 2782 | // (which are not kinematic and would, thus, move backwards). 2783 | bullet.body.data.collisionResponse = false; 2784 | // Let the bullets leave the world. 2785 | bullet.body.collideWorldBounds = false; 2786 | bullet.body.debug = DEBUG; 2787 | 2788 | this.customizer(bullet); 2789 | 2790 | return bullet; 2791 | }; 2792 | 2793 | /** 2794 | * Moves all alive bullets in this group forward, based on the given speed. 2795 | */ 2796 | BulletGroup.prototype.advance = function(speed) { 2797 | var length = this.children.length, bullet, i; 2798 | 2799 | for (i = 0; i < length; ++i) { 2800 | bullet = this.children[i]; 2801 | if (bullet.alive) { 2802 | bullet.move(speed); 2803 | } 2804 | } 2805 | }; 2806 | 2807 | /** 2808 | * A group of 'BulletTrail's. 2809 | * @param {Phaser.Game} game - A reference to the currently running Game. 2810 | * @constructor 2811 | */ 2812 | function BulletTrailGroup(game, _name) { 2813 | Group.call(this, game, 1, 'BulletTrails'); 2814 | this.classType = BulletTrail; 2815 | } 2816 | 2817 | BulletTrailGroup.prototype = Object.create(Group.prototype); 2818 | BulletTrailGroup.prototype.constructor = BulletTrailGroup; 2819 | 2820 | /** 2821 | * Creates a new BulletTrail object and adds it to the top of this group. 2822 | * Only the 'x', 'y' arguments given to this method are taken into account. 2823 | */ 2824 | BulletTrailGroup.prototype.create = function(x, y, _key, _frame, _exists) { 2825 | var trail = Group.prototype.create.call(this, x, y, null, null, true); 2826 | 2827 | if (DEBUG) this.extendChildName(trail); 2828 | trail.scale.copyFrom(this.finalScale); 2829 | return trail; 2830 | }; 2831 | 2832 | /** 2833 | * Sets the scaling values for all the children of the group. 2834 | * @param {Phaser.Point} worldScale - The scale of the world. 2835 | */ 2836 | BulletTrailGroup.prototype.resize = function(worldScale) { 2837 | var oldWorldScaleX = 1 / this.finalScale.x, 2838 | oldWorldScaleY = 1 / this.finalScale.y, 2839 | newScale = new Phaser.Point(1, 1), 2840 | length = this.children.length, trail, i; 2841 | 2842 | this.finalScale.set(1 / worldScale.x, 1 / worldScale.y); 2843 | for (i = 0; i < length; ++i) { 2844 | trail = this.children[i]; 2845 | // scale * oldWorldScale = (1/oldWorldScale * factor) * oldWorldScale = factor 2846 | newScale.set(trail.scale.x*oldWorldScaleX / worldScale.x, 2847 | trail.scale.y*oldWorldScaleY / worldScale.y); 2848 | trail.scale.copyFrom(newScale); 2849 | } 2850 | }; 2851 | 2852 | 2853 | /***** Objects for graphics or UI *****/ 2854 | 2855 | /** 2856 | * An object that draws trails behind bullets. 2857 | * @param {Phaser.Game} game - A reference to the currently running Game. 2858 | * @constructor 2859 | */ 2860 | function BulletTrailPainter(game) { 2861 | this.game = game; 2862 | this.trails = new BulletTrailGroup(game); 2863 | 2864 | // Dictionary for fast lookups. 2865 | this._liveTrails = {}; 2866 | } 2867 | 2868 | /** 2869 | * Handles resizing of all bullet trails. 2870 | * Useful when the game world is rescaled. 2871 | */ 2872 | BulletTrailPainter.prototype.resize = function() { 2873 | this.trails.resize(this.game.world.scale); 2874 | }; 2875 | 2876 | BulletTrailPainter.prototype.startTrail = function(bullet) { 2877 | var trail = this.trails.getFirstDead(CREATE_IF_NULL, bullet.x, bullet.y); 2878 | 2879 | trail.start(bullet); 2880 | this._liveTrails[bullet.name] = trail; 2881 | }; 2882 | 2883 | BulletTrailPainter.prototype.stopTrail = function(bullet) { 2884 | var trail = this._liveTrails[bullet.name]; 2885 | 2886 | // Check if the trail exists, since the handler may be called more than once! 2887 | if (trail) { 2888 | trail.stop(); 2889 | delete this._liveTrails[bullet.name]; 2890 | } 2891 | }; 2892 | 2893 | BulletTrailPainter.prototype._getTrailLength = function(trail) { 2894 | var bullet = trail.bullet; 2895 | // Note that we account for world scaling. 2896 | return Math.min( 2897 | this.game.math.distance(bullet.x, bullet.y, trail.refX, trail.refY), 2898 | trail.texture.width / this.game.world.scale.x); 2899 | }; 2900 | 2901 | BulletTrailPainter.prototype.updateTrails = function(elapsedTime) { 2902 | var trails = this.trails.children, trail, i; 2903 | 2904 | for (i = 0; i < trails.length; ++i) { 2905 | trail = trails[i]; 2906 | if (!trail.alive) continue; 2907 | 2908 | if (trail.bullet !== null) { // Live 2909 | // Don't round this value, since it causes jitter. 2910 | trail.width = this._getTrailLength(trail); 2911 | trail.x = trail.bullet.x; 2912 | trail.y = trail.bullet.y; 2913 | } else { // Stray 2914 | // Don't round this value, since it causes jitter. 2915 | trail.width -= (Supercold.speeds.bullet.normal * elapsedTime); 2916 | if (trail.width <= 0) { 2917 | trail.kill(); 2918 | } 2919 | } 2920 | } 2921 | }; 2922 | 2923 | 2924 | /** 2925 | * Abstract base class for heads-up displays. 2926 | * @param {Phaser.Game} game - A reference to the currently running Game. 2927 | * @param {Phaser.Group} group - The group in which to add the HUD. 2928 | * @param {number} x - The x-coordinate of the top-left corner of the HUD. 2929 | * @param {number} y - The y-coordinate of the top-left corner of the HUD. 2930 | * @param {number} width - The width of the HUD. 2931 | * @param {number} height - The height of the HUD. 2932 | * @param {string} key - A key for the Phaser chache. 2933 | * @constructor 2934 | * @abstract 2935 | */ 2936 | function HUD(game, group, x, y, width, height, key) { 2937 | var scale = game.camera.scale, 2938 | // Round, don't ceil! 2939 | scaledWidth = Math.round(width * scale.x), 2940 | scaledHeight = Math.round(height * scale.y); 2941 | 2942 | this.game = game; 2943 | 2944 | this.width = width; 2945 | this.height = height; 2946 | 2947 | this.hud = game.make.bitmapData(scaledWidth, scaledHeight, key, true); 2948 | this.hudImage = game.add.image(x, y, this.hud, null, group); 2949 | 2950 | this.hudImage.name = key; 2951 | // HUDs will be added in an unscaled group, so no need to account for scaling. 2952 | } 2953 | 2954 | /** 2955 | * Handles resizing of the HUD. 2956 | * Useful when the camera scale changes. 2957 | * @param {number} x - The x-coordinate of the top-left corner of the HUD. 2958 | * @param {number} y - The y-coordinate of the top-left corner of the HUD. 2959 | */ 2960 | HUD.prototype.resize = function(x, y) { 2961 | var scale = this.game.camera.scale, 2962 | // Round, don't ceil! 2963 | scaledWidth = Math.round(this.width * scale.x), 2964 | scaledHeight = Math.round(this.height * scale.y); 2965 | 2966 | this.hud.resize(scaledWidth, scaledHeight); 2967 | this.hudImage.x = x; 2968 | this.hudImage.y = y; 2969 | }; 2970 | 2971 | HUD.prototype.update = function() { 2972 | throw new Error('Abstract base class'); 2973 | }; 2974 | 2975 | 2976 | /** 2977 | * @param {Phaser.Game} game - A reference to the currently running Game. 2978 | * @param {Phaser.Group} group - The group in which to add the HUD. 2979 | * @param {number} x - The x-coordinate of the top-left corner of the minimap. 2980 | * @param {number} y - The y-coordinate of the top-left corner of the minimap. 2981 | * @param {number} width - The width of the minimap. 2982 | * @param {number} height - The height of the minimap. 2983 | * @constructor 2984 | */ 2985 | function Minimap(game, group, x, y, width, height) { 2986 | HUD.call(this, game, group, x, y, width, height, 'Minimap'); 2987 | 2988 | this._ratio = { 2989 | x: Supercold.minimap.width / game.world.bounds.width, 2990 | y: Supercold.minimap.height / game.world.bounds.height 2991 | }; 2992 | 2993 | this.hud.ctx.fillStyle = Supercold.style.minimap.background.color; 2994 | this.hud.ctx.strokeStyle = Supercold.style.minimap.border.color; 2995 | this.hud.ctx.lineWidth = Supercold.style.minimap.border.lineWidth; 2996 | this.hud.ctx.lineJoin = 'round'; 2997 | } 2998 | 2999 | Minimap.prototype = Object.create(HUD.prototype); 3000 | Minimap.prototype.constructor = Minimap; 3001 | 3002 | /** 3003 | * Handles resizing of the minimap. 3004 | * Useful when the camera scale changes. 3005 | * @param {number} x - The x-coordinate of the top-left corner of the minimap. 3006 | * @param {number} y - The y-coordinate of the top-left corner of the minimap. 3007 | */ 3008 | Minimap.prototype.resize = function(x, y) { 3009 | HUD.prototype.resize.call(this, x, y); 3010 | // Resizing may cause the canvas state to reset! 3011 | this.hud.ctx.fillStyle = Supercold.style.minimap.background.color; 3012 | this.hud.ctx.strokeStyle = Supercold.style.minimap.border.color; 3013 | this.hud.ctx.lineWidth = Supercold.style.minimap.border.lineWidth; 3014 | }; 3015 | 3016 | Minimap.prototype._markThrowable = function(throwable) { 3017 | var ctx = this.hud.ctx; 3018 | 3019 | // Just draw a circle. 3020 | circle(ctx, throwable.x * this._ratio.x, throwable.y * this._ratio.y, 3021 | Supercold.style.minimap.throwable.radius); 3022 | ctx.fill(); 3023 | }; 3024 | 3025 | Minimap.prototype._markEntity = function(entity, radius) { 3026 | var ctx = this.hud.ctx; 3027 | 3028 | // Just draw a circle. 3029 | circle(ctx, entity.x * this._ratio.x, entity.y * this._ratio.y, radius); 3030 | ctx.fill(); 3031 | ctx.stroke(); 3032 | }; 3033 | 3034 | Minimap.prototype._markPlayer = function(player) { 3035 | this._markEntity(player, Supercold.style.minimap.player.radius); 3036 | }; 3037 | Minimap.prototype._markBot = function(bot) { 3038 | this._markEntity(bot, Supercold.style.minimap.bot.radius); 3039 | }; 3040 | 3041 | /** 3042 | * 3043 | * @param {Phaser.Sprite} player - The player. 3044 | * @param {Phaser.Group} bots - The bots. 3045 | * @param {Phaser.Group} throwables - A group of throwable objects. 3046 | */ 3047 | Minimap.prototype.update = function(player, bots, throwables) { 3048 | var padX = Math.round(PADDING.width * this._ratio.x), 3049 | padY = Math.round(PADDING.height * this._ratio.y), 3050 | ctx = this.hud.ctx, style; 3051 | 3052 | ctx.save(); 3053 | ctx.scale(this.game.camera.scale.x, this.game.camera.scale.y); 3054 | 3055 | // Clear the map. Necessary since background is semi-transparent. 3056 | ctx.clearRect(0, 0, this.width, this.height); 3057 | ctx.fillRect(0, 0, this.width, this.height); 3058 | ctx.strokeRect(0, 0, this.width, this.height); 3059 | 3060 | ctx.strokeStyle = Supercold.style.minimap.innerBorder.color; 3061 | ctx.lineWidth = Supercold.style.minimap.innerBorder.lineWidth; 3062 | ctx.strokeRect(padX, padY, this.width - 2*padX, this.height - 2*padY); 3063 | 3064 | // The (0, 0) point of our world is in the center! 3065 | ctx.translate(this.width / 2, this.height / 2); 3066 | 3067 | ctx.fillStyle = Supercold.style.minimap.throwable.color; 3068 | //throwables.forEachAlive(this._markThrowable, this); 3069 | 3070 | style = Supercold.style.minimap.bot; 3071 | ctx.fillStyle = style.color; 3072 | ctx.strokeStyle = style.strokeStyle; 3073 | ctx.lineWidth = style.lineWidth; 3074 | bots.forEachAlive(this._markBot, this); 3075 | 3076 | style = Supercold.style.minimap.player; 3077 | ctx.fillStyle = style.color; 3078 | ctx.strokeStyle = style.strokeStyle; 3079 | ctx.lineWidth = style.lineWidth; 3080 | this._markPlayer(player); 3081 | ctx.restore(); 3082 | // Re-render! 3083 | this.hud.dirty = true; 3084 | }; 3085 | 3086 | 3087 | /** 3088 | * @param {Phaser.Game} game - A reference to the currently running Game. 3089 | * @param {Phaser.Group} group - The group in which to add the HUD. 3090 | * @param {number} x - The x-coordinate of the top-left corner of the reload bar. 3091 | * @param {number} y - The y-coordinate of the top-left corner of the reload bar. 3092 | * @param {number} width - The width of the reload bar. 3093 | * @param {number} height - The height of the reload bar. 3094 | * @param {string} key - A key for the Phaser chache. 3095 | * @constructor 3096 | */ 3097 | function ReloadBar(game, group, x, y, width, height, key, fillStyle) { 3098 | HUD.call(this, game, group, x, y, Supercold.bar.width, Supercold.bar.height, key); 3099 | 3100 | this._fillStyle = fillStyle; 3101 | this._tween = null; 3102 | 3103 | this._fill(); 3104 | } 3105 | 3106 | ReloadBar.prototype = Object.create(HUD.prototype); 3107 | ReloadBar.prototype.constructor = ReloadBar; 3108 | 3109 | ReloadBar.prototype._fill = function() { 3110 | this.hud.rect(0, 0, this.hud.width, this.hud.height, this._fillStyle); 3111 | }; 3112 | 3113 | /** 3114 | * Handles resizing of the reload bar. 3115 | * Useful when the camera scale changes. 3116 | * @param {number} x - The x-coordinate of the top-left corner of the reload bar. 3117 | * @param {number} y - The y-coordinate of the top-left corner of the reload bar. 3118 | */ 3119 | ReloadBar.prototype.resize = function(x, y) { 3120 | HUD.prototype.resize.call(this, x, y); 3121 | // Resizing clears the canvas! 3122 | this._fill(); 3123 | }; 3124 | 3125 | /** 3126 | * @param {number} progress - A value in the range [0, 1]. 3127 | */ 3128 | ReloadBar.prototype.update = function(progress) { 3129 | this.hudImage.scale.x = progress / 1; 3130 | this.hudImage.alpha = (progress === 1) ? 1 : 0.75; 3131 | }; 3132 | 3133 | ReloadBar.prototype._removeTween = function() { 3134 | this._tween = null; 3135 | }; 3136 | 3137 | ReloadBar.prototype.shake = function() { 3138 | var duration = this.game.time.physicsElapsedMS; 3139 | 3140 | // Already shaking! 3141 | if (this._tween) return; 3142 | 3143 | // The element is fixed to camera, so use the cameraOffset property. 3144 | this._tween = this.game.add.tween(this.hudImage.cameraOffset).to({ 3145 | x: '-1' 3146 | }, duration, Phaser.Easing.Linear.None); 3147 | this._tween.to({ 3148 | x: '+2' 3149 | }, duration, Phaser.Easing.Linear.None, !AUTOSTART, 0, 15, true); 3150 | this._tween.to({ 3151 | x: '-1' 3152 | }, duration, Phaser.Easing.Linear.None); 3153 | this._tween.onComplete.add(this._removeTween, this); 3154 | this._tween.start(); 3155 | }; 3156 | 3157 | /** 3158 | * @param {Phaser.Game} game - A reference to the currently running Game. 3159 | * @param {Phaser.Group} group - The group in which to add the HUD. 3160 | * @param {number} x - The x-coordinate of the top-left corner of the hotswitch bar. 3161 | * @param {number} y - The y-coordinate of the top-left corner of the hotswitch bar. 3162 | * @param {number} width - The width of the hotswitch bar. 3163 | * @param {number} height - The height of the hotswitch bar. 3164 | * @constructor 3165 | */ 3166 | function HotswitchBar(game, group, x, y, width, height) { 3167 | ReloadBar.call(this, game, group, x, y, width, height, 'HotswitchBar', 3168 | Supercold.style.hotswitchBar.color); 3169 | } 3170 | 3171 | HotswitchBar.prototype = Object.create(ReloadBar.prototype); 3172 | HotswitchBar.prototype.constructor = HotswitchBar; 3173 | 3174 | /** 3175 | * @param {Phaser.Game} game - A reference to the currently running Game. 3176 | * @param {Phaser.Group} group - The group in which to add the HUD. 3177 | * @param {number} x - The x-coordinate of the top-left corner of the bullet bar. 3178 | * @param {number} y - The y-coordinate of the top-left corner of the bullet bar. 3179 | * @param {number} width - The width of the bullet bar. 3180 | * @param {number} height - The height of the bullet bar. 3181 | * @constructor 3182 | */ 3183 | function BulletBar(game, group, x, y, width, height) { 3184 | ReloadBar.call(this, game, group, x, y, width, height, 'BulletBar', 3185 | Supercold.style.bulletBar.color); 3186 | } 3187 | 3188 | BulletBar.prototype = Object.create(ReloadBar.prototype); 3189 | BulletBar.prototype.constructor = BulletBar; 3190 | 3191 | 3192 | /***** The core of the game *****/ 3193 | 3194 | /** 3195 | * @constructor 3196 | */ 3197 | Supercold.Game = function(game) { 3198 | Supercold._BaseState.call(this, game); 3199 | 3200 | this._sprites = { 3201 | player: null, 3202 | background: null 3203 | }; 3204 | this._groups = { 3205 | bounds: null, 3206 | bots: null, 3207 | playerBullets: null, 3208 | botBullets: null, 3209 | throwables: null, 3210 | ui: null 3211 | }; 3212 | this._colGroups = { // Collision groups 3213 | player: null, 3214 | bots: null, 3215 | playerBullets: null, 3216 | botBullets: null, 3217 | throwables: null 3218 | }; 3219 | this._controls = { 3220 | cursors: null, 3221 | wasd: null, 3222 | fireKey: null, 3223 | dodgeKey: null 3224 | }; 3225 | this._huds = { // Heads-up displays. 3226 | minimap: null, 3227 | bulletBar: null, 3228 | hotswitchBar: null, 3229 | bulletCount: null 3230 | }; 3231 | this._weapons = { 3232 | pistol: null, 3233 | burst: null, 3234 | burst3: null, 3235 | blunderbuss: null, 3236 | shotgun: null, 3237 | dbshotgun: null, 3238 | rifle: null 3239 | }; 3240 | this._counts = { 3241 | // These will be set in init. 3242 | bots: -1, 3243 | bullets: -1 3244 | }; 3245 | this._next = { // 3246 | // Time remaining since next bot spawn. 3247 | botTime: 0, 3248 | // Time remaining since next hotswitch. 3249 | hotSwitch: 0 3250 | }; 3251 | this._cached = { // Internal cached objects. 3252 | verVec: {x: 0, y: 0}, 3253 | horVec: {x: 0, y: 0} 3254 | }; 3255 | 3256 | this._mutators = null; 3257 | this._bulletTrailPainter = null; 3258 | this._announcer = null; 3259 | this._overlay = null; 3260 | this._superhotFx = null; 3261 | 3262 | // This will be set in init. 3263 | this.level = -1; 3264 | 3265 | this._elapsedTime = 0; 3266 | this._hotswitching = false; 3267 | }; 3268 | 3269 | Supercold.Game.prototype = Object.create(Supercold._BaseState.prototype); 3270 | Supercold.Game.prototype.constructor = Supercold.Game; 3271 | 3272 | Object.defineProperty(Supercold.Game.prototype, 'superhot', { 3273 | get: function() { 3274 | return (this._counts.bots === 0 && this._groups.bots.countLiving() === 0); 3275 | } 3276 | }); 3277 | 3278 | Object.defineProperty(Supercold.Game.prototype, '_hotswitchTimeout', { 3279 | get: function() { 3280 | return (this._mutators.superhotswitch) ? 3281 | Supercold.superhotswitchTimeout : Supercold.hotswitchTimeout; 3282 | } 3283 | }); 3284 | 3285 | Object.defineProperty(Supercold.Game.prototype, '_spriteScale', { 3286 | get: function() { 3287 | if (this._mutators.bighead && this._mutators.chibi) return 1; 3288 | if (this._mutators.bighead) return Supercold.bigheadScale; 3289 | if (this._mutators.chibi) return Supercold.chibiScale; 3290 | return 1; 3291 | } 3292 | }); 3293 | 3294 | 3295 | Supercold.Game.prototype.init = function(options) { 3296 | this.level = options.level; 3297 | this._mutators = Supercold.storage.loadMutators(); 3298 | switch (this.level) { 3299 | // More bots for boss levels! 3300 | case 9: 3301 | case 19: 3302 | case 29: 3303 | case 39: 3304 | case 49: 3305 | case 74: 3306 | this._counts.bots = 6 + this.level; 3307 | break; 3308 | default: 3309 | if (this.level % 10 === 0) { 3310 | this._counts.bots = this.level; 3311 | } else { 3312 | this._counts.bots = 5 + Math.floor(this.level * 0.666); 3313 | } 3314 | break; 3315 | } 3316 | this._counts.bullets = (this._mutators.lmtdbull) ? this._counts.bots*3 : -1; 3317 | }; 3318 | 3319 | 3320 | function bulletHandler(myBullet, otherBullet) { 3321 | myBullet.sprite.kill(); 3322 | // The other bullet will be killed by the other handler call. 3323 | } 3324 | 3325 | function startTrail(bullet) { 3326 | /*jshint validthis: true */ 3327 | this._bulletTrailPainter.startTrail(bullet); 3328 | } 3329 | function stopTrail(bullet) { 3330 | /*jshint validthis: true */ 3331 | this._bulletTrailPainter.stopTrail(bullet); 3332 | } 3333 | 3334 | Supercold.Game.prototype._addBulletGroups = function() { 3335 | var self = this; 3336 | 3337 | function customizeBullet(bullet, myColGroup) { 3338 | bullet.events.onRevived.add(startTrail, self); 3339 | bullet.events.onKilled.add(stopTrail, self); 3340 | bullet.body.setCollisionGroup(myColGroup); 3341 | // All bullets collide with all other bullets and all live entities. 3342 | bullet.body.collides([self._colGroups.player, self._colGroups.bots]); 3343 | bullet.body.collides([self._colGroups.playerBullets, self._colGroups.botBullets], 3344 | bulletHandler, self); 3345 | } 3346 | 3347 | function customizePlayerBullet(bullet) { 3348 | customizeBullet(bullet, self._colGroups.playerBullets); 3349 | } 3350 | function customizeBotBullet(bullet) { 3351 | customizeBullet(bullet, self._colGroups.botBullets); 3352 | } 3353 | 3354 | this._groups.playerBullets = new BulletGroup( 3355 | this.game, 1, customizePlayerBullet, 'Player Bullets'); 3356 | this._groups.botBullets = new BulletGroup( 3357 | this.game, 1, customizeBotBullet, 'Bot Bullets'); 3358 | }; 3359 | 3360 | Supercold.Game.prototype._addPlayerBound = function(rect, i, collisionGroup) { 3361 | var bound = this._groups.bounds.create(rect.x, rect.y, null); 3362 | 3363 | bound.name = 'Bound ' + i; 3364 | bound.body.static = true; 3365 | bound.body.setRectangle( 3366 | rect.width, rect.height, rect.halfWidth, rect.halfHeight); 3367 | bound.body.setCollisionGroup(collisionGroup); 3368 | // The bounds collide with the player. 3369 | if (!DEBUG) { 3370 | bound.body.collides(this._colGroups.player); 3371 | } 3372 | bound.body.debug = DEBUG; 3373 | }; 3374 | 3375 | /** 3376 | * Creates boundaries around the world that restrict the player's movement. 3377 | */ 3378 | Supercold.Game.prototype._addPlayerBounds = function(collisionGroup) { 3379 | var bounds = this.world.bounds; 3380 | 3381 | this._groups.bounds = this.add.physicsGroup(PHYSICS_SYSTEM); 3382 | this._groups.bounds.name = 'Player Bounds'; 3383 | // Note that all invisible walls have double the width or height in order to 3384 | // prevent the player from getting out of the world bounds when hotswitching. 3385 | [new Phaser.Rectangle( 3386 | // Top 3387 | bounds.left - PADDING.width, // x 3388 | bounds.top - PADDING.height, // y 3389 | bounds.width + 2*PADDING.width, // width 3390 | PADDING.height * 2 // height 3391 | ), new Phaser.Rectangle( 3392 | // Bottom 3393 | bounds.left - PADDING.width, 3394 | bounds.bottom - PADDING.height, 3395 | bounds.width + 2*PADDING.width, 3396 | PADDING.height * 2 3397 | ), new Phaser.Rectangle( 3398 | // Left 3399 | bounds.left - PADDING.width, 3400 | bounds.top - PADDING.height, 3401 | PADDING.width * 2, 3402 | bounds.height + 2*PADDING.height 3403 | ), new Phaser.Rectangle( 3404 | // Right 3405 | bounds.right - PADDING.width, 3406 | bounds.top - PADDING.height, 3407 | PADDING.width * 2, 3408 | bounds.height + 2*PADDING.height 3409 | )].forEach(function(rect, index) { 3410 | this._addPlayerBound(rect, index, collisionGroup); 3411 | }, this); 3412 | }; 3413 | 3414 | Supercold.Game.prototype._addBotInputHandler = function() { 3415 | var properties0 = { 3416 | alpha: 0 3417 | }, properties1 = { 3418 | alpha: 1 3419 | }; 3420 | 3421 | this._groups.bots.onChildInputDown.add(function hotswitch(bot, pointer) { 3422 | var duration1 = 250, duration2 = 200, 3423 | player = this._sprites.player, 3424 | playerTween, botTweeen; 3425 | 3426 | if (DEBUG) log('Clicked on bot'); 3427 | // NOTE: Don't use Ctrl + left lick since it is a simulated right click! 3428 | // Check for the appropriate controls. 3429 | if (!((pointer.leftButton.isDown && pointer.leftButton.shiftKey) || 3430 | pointer.rightButton.isDown)) { 3431 | return; 3432 | } 3433 | // The hotswitch may not be ready yet 3434 | if (this._next.hotSwitch > 0) { 3435 | this._huds.hotswitchBar.shake(); 3436 | return; 3437 | } 3438 | // or we may be in the middle of it. 3439 | if (this._hotswitching) { 3440 | return; 3441 | } 3442 | 3443 | if (DEBUG) log('Hotswitching...'); 3444 | this._hotswitching = true; 3445 | // Fade out. 3446 | playerTween = this.add.tween(player).to( 3447 | properties0, duration1, Phaser.Easing.Linear.None, AUTOSTART); 3448 | botTweeen = this.add.tween(bot).to( 3449 | properties0, duration1, Phaser.Easing.Linear.None, AUTOSTART); 3450 | botTweeen.onComplete.addOnce(function swap() { 3451 | var player = this._sprites.player, temp; 3452 | 3453 | // Make the camera move more smoothly for the hotswitch. 3454 | this.camera.follow(player, Phaser.Camera.FOLLOW_LOCKON, 0.2, 0.2); 3455 | 3456 | temp = player.body.x; 3457 | player.body.x = bot.body.x; 3458 | bot.body.x = temp; 3459 | temp = player.body.y; 3460 | player.body.y = bot.body.y; 3461 | bot.body.y = temp; 3462 | 3463 | this.camera.flash(0xEEEAE0, 300); 3464 | 3465 | // Fade in. 3466 | playerTween.to( 3467 | properties1, duration2, Phaser.Easing.Quadratic.Out, AUTOSTART); 3468 | botTweeen.to( 3469 | properties1, duration2, Phaser.Easing.Quadratic.Out, AUTOSTART); 3470 | this._next.hotSwitch = this._hotswitchTimeout; 3471 | botTweeen.onComplete.addOnce(function endHotswitch() { 3472 | // Reset the camera to its default behaviour. 3473 | this.camera.follow(player); 3474 | this._hotswitching = false; 3475 | if (DEBUG) log('Hotswitched!'); 3476 | }, this); 3477 | }, this); 3478 | }, this); 3479 | }; 3480 | 3481 | /** 3482 | * Returns an appropriate offset value for a background scrolling effect. 3483 | * Our background sprite contains an additional row/column on each side 3484 | * of the inner region (light cells) to allow for the scrolling effect. 3485 | * @param {number} playerDistance - The distance of the player from the center. 3486 | * @param {number} bgMaxDistance - The max distance that the background will travel. 3487 | * @return {number} - The background offset. 3488 | */ 3489 | function bgOffset(playerDistance, bgMaxDistance) { 3490 | var CELLDIM = Supercold.cell.width; 3491 | // Bound the value. 3492 | return CELLDIM * Math.min( 3493 | Math.floor(bgMaxDistance / CELLDIM) - 1, 3494 | Math.floor(playerDistance / CELLDIM)); 3495 | } 3496 | 3497 | /** 3498 | * Places the background in a position such that it looks likes it is scrolled. 3499 | * Our background sprite contains an additional row/column on each side 3500 | * of the inner region (light cells) to allow for the scrolling effect. 3501 | */ 3502 | Supercold.Game.prototype._scrollBackground = function() { 3503 | var player = this._sprites.player, background = this._sprites.background; 3504 | 3505 | background.x = (player.x > 0) ? 3506 | bgOffset( player.x, Supercold.world.width/2 - PADDING.width) : 3507 | -bgOffset(-player.x, Supercold.world.width/2 - PADDING.width); 3508 | background.y = (player.y > 0) ? 3509 | bgOffset( player.y, Supercold.world.height/2 - PADDING.height) : 3510 | -bgOffset(-player.y, Supercold.world.height/2 - PADDING.height); 3511 | }; 3512 | 3513 | Supercold.Game.prototype._positionHuds = function() { 3514 | var camera = this.camera, scale = camera.scale, 3515 | hud = this._huds.minimap.hudImage, refHud; 3516 | 3517 | // NOTE: Do not set anchor.x to 1, because this makes reload bars 3518 | // reload right to left! Let's handle anchoring ourselves instead. 3519 | 3520 | hud.right = camera.width - Math.round(Supercold.minimap.x * scale.x); 3521 | hud.bottom = camera.height - Math.round(Supercold.minimap.y * scale.y); 3522 | 3523 | // At least 2 units on the y-axis so that bars do not touch on small screens! 3524 | refHud = hud; 3525 | hud = this._huds.hotswitchBar.hudImage; 3526 | hud.alignTo(refHud, Phaser.TOP_LEFT, 0, Math.round(2 * scale.y)); 3527 | refHud = hud; 3528 | hud = this._huds.bulletBar.hudImage; 3529 | hud.alignTo(refHud, Phaser.TOP_LEFT, 0, Math.round(2 * scale.y)); 3530 | 3531 | if (this._huds.bulletCount) { 3532 | hud = this._huds.bulletCount; 3533 | hud.right = camera.width - Math.round(Supercold.bulletCount.x * scale.x); 3534 | hud.top = Math.round(Supercold.bulletCount.y * scale.y); 3535 | } 3536 | }; 3537 | 3538 | Supercold.Game.prototype._lose = function(player, bullet, _playerS, _bulletS) { 3539 | var duration = 1500; 3540 | 3541 | // The collision handler may be called more than once due to bullet shapes! 3542 | if (!bullet.sprite.alive) { 3543 | return; 3544 | } 3545 | bullet.sprite.kill(); 3546 | // More than one bullet may collide with the player at once! 3547 | if (!player.sprite.alive) { 3548 | return; 3549 | } 3550 | // If the player gets a second chance to live, don't lose! 3551 | if (this._mutators.secondchance) { 3552 | this._mutators.secondchance = false; 3553 | return; 3554 | } 3555 | // If we have already won or we are in godmode, don't lose! 3556 | if (this.superhot || this._mutators.godmode) { 3557 | return; 3558 | } 3559 | 3560 | this._overlay = this._groups.ui.add(newOverlay(this.game)); 3561 | this._overlay.name = 'lose screen overlay'; 3562 | this._overlay.alpha = 0; 3563 | this.add.tween(this._overlay).to({ 3564 | alpha: 1 3565 | }, duration, Phaser.Easing.Linear.None, AUTOSTART) 3566 | .onComplete.addOnce(function restart() { 3567 | this.time.events.add(Phaser.Timer.SECOND * 1.5, this.restart, this); 3568 | this.time.events.add(Phaser.Timer.SECOND * 1.5, hideTip, this); 3569 | }, this); 3570 | 3571 | // Can't hotswitch when dead. 3572 | this._groups.bots.onChildInputDown.removeAll(); 3573 | player.removeCollisionGroup( 3574 | [this._colGroups.playerBullets, this._colGroups.botBullets]); 3575 | if (DEBUG) log('Removed input and collision handlers.'); 3576 | player.sprite.kill(); 3577 | // TODO: Add fancy effects. 3578 | this.camera.shake(0.00086, 1200, true, Phaser.Camera.SHAKE_HORIZONTAL, false); 3579 | showTipRnd(); 3580 | }; 3581 | 3582 | Supercold.Game.prototype._superhot = function() { 3583 | var DELAY = 50, newLevel = this.level + 1, announcer; 3584 | 3585 | this._sprites.player.body.setZeroVelocity(); 3586 | 3587 | // TODO: Add fancy effect. 3588 | Supercold.storage.saveLevel(newLevel); 3589 | // Create the announcer here to avoid lag and desync with the sound fx. 3590 | announcer = new Announcer(this.game, Supercold.texts.SUPERHOT, { 3591 | group: this._groups.ui, 3592 | nextDelay: 650, 3593 | finalDelay: 400, 3594 | repeat: true, 3595 | overlay: true, 3596 | overlayColor: 'light' 3597 | }); 3598 | this.time.events.add(DELAY, function superhot() { 3599 | var superDuration = announcer.options.nextDelay + announcer.options.duration, 3600 | hotDuration = announcer.options.finalDelay + announcer.options.duration, 3601 | duration = superDuration + hotDuration, 3602 | times = 3, delay = times * duration, i; 3603 | 3604 | for (i = 0; i < times; ++i) { 3605 | this.time.events.add(i*duration, function saySuper() { 3606 | this._superhotFx.play('super'); 3607 | }, this); 3608 | this.time.events.add(i*duration + superDuration, function sayHot() { 3609 | this._superhotFx.play('hot'); 3610 | }, this); 3611 | } 3612 | this._announcer = announcer.announce(); 3613 | 3614 | this.time.events.add(delay, function nextLevel() { 3615 | this.state.start('Game', CLEAR_WORLD, !CLEAR_CACHE, { 3616 | level: newLevel 3617 | }); 3618 | }, this); 3619 | }, this); 3620 | }; 3621 | 3622 | Supercold.Game.prototype._botKillHandler = function(bot, bullet, _botS, _bulletS) { 3623 | // The collision handler may be called more than once due to bullet shapes! 3624 | if (!bot.sprite.alive) { 3625 | return; 3626 | } 3627 | 3628 | bot.sprite.kill(); 3629 | bullet.sprite.kill(); 3630 | if (this.superhot) { 3631 | this._superhot(); // SUPER HOT! 3632 | } 3633 | }; 3634 | 3635 | /** 3636 | * Creates some reusable weapons for the bots. 3637 | */ 3638 | Supercold.Game.prototype._createBotWeapons = function() { 3639 | var bullets = this._groups.botBullets, weapons = this._weapons, weapon; 3640 | 3641 | weapons.pistol = new Pistol(bullets, 1); 3642 | weapons.burst = new Burst(bullets, 1); 3643 | weapons.burst3 = new Burst3(bullets, 1); 3644 | weapons.blunderbuss = new Blunderbuss(this.game, bullets, 1); 3645 | weapons.shotgun = new Shotgun(this.game, bullets, 1); 3646 | weapons.dbshotgun = new DbShotgun(this.game, bullets, 1); 3647 | weapons.rifle = new Rifle(this.game, bullets, 1); 3648 | 3649 | if (DEBUG) { 3650 | // Make sure we created all the weapons. 3651 | for (weapon in weapons) { 3652 | if (weapons.hasOwnProperty(weapon)) { 3653 | assert(weapon !== undefined); 3654 | } 3655 | } 3656 | } 3657 | }; 3658 | 3659 | /** 3660 | * @return {Weapon} - A weapon for the player. 3661 | */ 3662 | Supercold.Game.prototype._createPlayerWeapon = function() { 3663 | var fireFactor = (this._mutators.fastgun) ? Supercold.fastFireFactor : 1, 3664 | bullets = this._groups.playerBullets, 3665 | guns = Supercold.storage.loadGuns(); 3666 | 3667 | if (guns.rifle) { 3668 | return new Rifle(this.game, bullets, fireFactor); 3669 | } else if (guns.dbshotgun) { 3670 | return new DbShotgun(this.game, bullets, fireFactor); 3671 | } else if (guns.shotgun) { 3672 | return new Shotgun(this.game, bullets, fireFactor); 3673 | } else if (guns.blunderbuss) { 3674 | return new Blunderbuss(this.game, bullets, fireFactor); 3675 | } else if (guns.burst3) { 3676 | return new Burst3(bullets, fireFactor); 3677 | } else if (guns.burst) { 3678 | return new Burst(bullets, fireFactor); 3679 | } else if (guns.pistol) { 3680 | return new Pistol(bullets, fireFactor); 3681 | } else { 3682 | if (DEBUG) { 3683 | assert(false); 3684 | } else { 3685 | return new Pistol(bullets, fireFactor); 3686 | } 3687 | } 3688 | }; 3689 | 3690 | Supercold.Game.prototype._assignBotWeapon = function(bot) { 3691 | // All bots share the same weapons. Assign one randomly. 3692 | var chance = this.rnd.frac() * Math.max(0.2, 1 - (this.level - 1)/100); 3693 | 3694 | switch (this.level) { 3695 | // Boss battle for the level before unlocking a weapon! 3696 | case 9: 3697 | bot.weapon = this._weapons.burst; 3698 | return; 3699 | case 19: 3700 | bot.weapon = this._weapons.blunderbuss; 3701 | return; 3702 | case 29: 3703 | bot.weapon = this._weapons.shotgun; 3704 | return; 3705 | case 39: 3706 | bot.weapon = this._weapons.burst3; 3707 | return; 3708 | case 49: 3709 | bot.weapon = this._weapons.dbshotgun; 3710 | return; 3711 | case 74: 3712 | bot.weapon = this._weapons.rifle; 3713 | return; 3714 | // Regular levels. 3715 | default: 3716 | if (chance < 0.02) { 3717 | bot.weapon = this._weapons.rifle; 3718 | } else if (chance < 0.05) { 3719 | bot.weapon = this._weapons.dbshotgun; 3720 | } else if (chance < 0.20) { 3721 | bot.weapon = this._weapons.shotgun; 3722 | } else if (chance < 0.25) { 3723 | bot.weapon = this._weapons.blunderbuss; 3724 | } else if (chance < 0.42) { 3725 | bot.weapon = this._weapons.burst3; 3726 | } else if (chance < 0.66) { 3727 | bot.weapon = this._weapons.burst; 3728 | } else { 3729 | bot.weapon = this._weapons.pistol; 3730 | } 3731 | return; 3732 | } 3733 | }; 3734 | 3735 | Supercold.Game.prototype._setBotMovement = function(bot) { 3736 | var distance = 300, chance = this.rnd.frac(); 3737 | 3738 | if (this.level >= 20) { 3739 | if (chance < 2/10) { 3740 | bot.move = newStrafingMover( 3741 | this.rnd.between(distance, distance + 100), 3742 | this.rnd.sign() * Math.PI/2); 3743 | } else if (chance < 4/10) { 3744 | bot.move = newStrafingDistantMover( 3745 | this.rnd.between(distance, distance + 100), 3746 | this.rnd.sign() * Math.PI/2); 3747 | } else if (chance < 7/10) { 3748 | bot.move = newDistantMover( 3749 | this.rnd.between(distance, distance + 100)); 3750 | } else { 3751 | bot.move = newForwardMover(); 3752 | } 3753 | } else { 3754 | if (chance < 1/5) { 3755 | bot.move = newStrafingDistantMover( 3756 | this.rnd.between(distance, distance + 150), 3757 | this.rnd.sign() * Math.PI/2); 3758 | } else if (chance < 3/5) { 3759 | bot.move = newDistantMover( 3760 | this.rnd.between(distance, distance + 150)); 3761 | } else { 3762 | bot.move = newForwardMover(); 3763 | } 3764 | } 3765 | }; 3766 | 3767 | /** 3768 | * Spawns the next bot. 3769 | * @param {boolean} [closer=false] - If true, spawns the bot closer to the player. 3770 | */ 3771 | Supercold.Game.prototype._spawnBot = function(closer) { 3772 | var player = this._sprites.player, colGroups = this._colGroups, 3773 | radius = ((NATIVE_WIDTH + NATIVE_HEIGHT) / 2) / 2, 3774 | angle, x, y, bot; 3775 | 3776 | if (closer) { 3777 | radius -= (2 * Supercold.player.radius) * 2; 3778 | } 3779 | angle = this.rnd.realInRange(0, 2 * Math.PI); 3780 | x = player.x + (radius * Math.cos(angle)); 3781 | y = player.y + (radius * Math.sin(angle)); 3782 | 3783 | // If out of bounds, bring it back in. 3784 | if (x < 0) { 3785 | x = Math.max(x, this.world.bounds.left); 3786 | } else { 3787 | x = Math.min(x, this.world.bounds.right); 3788 | } 3789 | if (y < 0) { 3790 | y = Math.max(y, this.world.bounds.top); 3791 | } else { 3792 | y = Math.min(y, this.world.bounds.bottom); 3793 | } 3794 | 3795 | --this._counts.bots; 3796 | 3797 | bot = this._groups.bots.getFirstDead(CREATE_IF_NULL, x, y); 3798 | this._assignBotWeapon(bot); 3799 | this._setBotMovement(bot); 3800 | 3801 | // Fade into existence. 3802 | bot.alpha = 0.1; 3803 | this.add.tween(bot).to({ 3804 | alpha: 1 3805 | }, 750, Phaser.Easing.Linear.None, AUTOSTART); 3806 | 3807 | bot.body.setCollisionGroup(colGroups.bots); 3808 | // Bots collide against themselves, the player and all bullets. 3809 | bot.body.collides([colGroups.bots, colGroups.player]); 3810 | bot.body.collides([colGroups.botBullets, colGroups.playerBullets], 3811 | this._botKillHandler, this); 3812 | }; 3813 | 3814 | Supercold.Game.prototype._announce = function() { 3815 | var levelText; 3816 | 3817 | if (this.level === 1) { 3818 | // If the player just started playing, explain the game mechanics. 3819 | this._announcer = new Announcer(this.game, Supercold.texts.MECHANICS, { 3820 | group: this._groups.ui, 3821 | initDelay: 750, 3822 | duration: 250, 3823 | nextDelay: 250, 3824 | flashTint: 0x4A4A4A, 3825 | flashOffDuration: 475 3826 | }).announce(); 3827 | } else { 3828 | // Otherwise, simply tell in which level they are in. 3829 | levelText = Supercold.texts.LEVEL + ' ' + this.level; 3830 | this._announcer = new Announcer(this.game, [levelText], { 3831 | group: this._groups.ui, 3832 | initDelay: 750, 3833 | duration: 500, 3834 | finalDelay: 250, 3835 | flashTint: 0x4A4A4A, 3836 | flashOffDuration: 800 3837 | }).announce(); 3838 | } 3839 | }; 3840 | 3841 | 3842 | Supercold.Game.prototype.restart = function restart() { 3843 | hideTip(); 3844 | this.state.restart(CLEAR_WORLD, !CLEAR_CACHE, {level: this.level}); 3845 | }; 3846 | 3847 | Supercold.Game.prototype.quit = function quit() { 3848 | hideTip(); 3849 | log('\nThank you for playing! :)'); 3850 | this.state.start('MainMenu'); 3851 | }; 3852 | 3853 | 3854 | Supercold.Game.prototype.create = function() { 3855 | var radius = Supercold.player.radius * this._spriteScale, 3856 | player, boundsColGroup; 3857 | 3858 | if (DEBUG) log('Creating Game state: Level ' + this.level + '...'); 3859 | 3860 | // Scaling should be specified first. 3861 | this.setScaling(); 3862 | 3863 | this._sprites.background = this.addBackground(); 3864 | 3865 | this._superhotFx = this.add.audio('superhot'); 3866 | this._superhotFx.addMarker('super', 0, 0.825); 3867 | this._superhotFx.addMarker('hot', 0.825, 0.775); 3868 | 3869 | // Collision groups for the player, the bots, the bullets and the bounds. 3870 | this._colGroups.player = this.physics.p2.createCollisionGroup(); 3871 | this._colGroups.bots = this.physics.p2.createCollisionGroup(); 3872 | this._colGroups.playerBullets = this.physics.p2.createCollisionGroup(); 3873 | this._colGroups.botBullets = this.physics.p2.createCollisionGroup(); 3874 | boundsColGroup = this.physics.p2.createCollisionGroup(); 3875 | 3876 | this.physics.p2.updateBoundsCollisionGroup(); 3877 | 3878 | this._bulletTrailPainter = new BulletTrailPainter(this.game); 3879 | // Create the bullet groups first, so that they are rendered under the bots. 3880 | this._addBulletGroups(); 3881 | // Reusable weapons for the bots. 3882 | this._createBotWeapons(); 3883 | 3884 | this._addPlayerBounds(boundsColGroup); 3885 | 3886 | // The player is positioned in a semi-random location inside their bounds. 3887 | this._sprites.player = player = new Player( 3888 | this.game, 3889 | this.rnd.sign() * this.rnd.between( 3890 | 0, this.world.bounds.right - PADDING.width - radius), 3891 | this.rnd.sign() * this.rnd.between( 3892 | 0, this.world.bounds.bottom - PADDING.height - radius)); 3893 | player.weapon = this._createPlayerWeapon(); 3894 | player.body.setCollisionGroup(this._colGroups.player); 3895 | // The player collides with the bounds, the bots and the bullets. 3896 | player.body.collides([boundsColGroup, this._colGroups.bots]); 3897 | player.body.collides([this._colGroups.playerBullets, this._colGroups.botBullets], 3898 | this._lose, this); 3899 | 3900 | // The player is always in the center of the screen. 3901 | this.camera.follow(player); 3902 | 3903 | this._groups.bots = new BotGroup(this.game, this._spriteScale); 3904 | this._addBotInputHandler(); 3905 | 3906 | this._scrollBackground(); 3907 | 3908 | // Add HUDs. 3909 | this._groups.ui = this.add.group(undefined, 'UI group', ADD_TO_STAGE); 3910 | this._huds.minimap = new Minimap( 3911 | this.game, this._groups.ui, 0, 0, 3912 | Supercold.minimap.width, Supercold.minimap.height); 3913 | this._huds.hotswitchBar = new HotswitchBar( 3914 | this.game, this._groups.ui, 0, 0, 3915 | Supercold.bar.width, Supercold.bar.height); 3916 | this._huds.bulletBar = new BulletBar( 3917 | this.game, this._groups.ui, 0, 0, 3918 | Supercold.bar.width, Supercold.bar.height); 3919 | if (this._mutators.lmtdbull) { 3920 | this._huds.bulletCount = this.add.text(0, 0, 3921 | Supercold.texts.BULLETS + this._counts.bullets, 3922 | Supercold.wordStyles.BULLETS, this._groups.ui); 3923 | this._huds.bulletCount.fontSize = 3924 | this.getHudFontSize(Supercold.wordStyles.BULLETS.fontSize); 3925 | } 3926 | // and fix their position on the screen. 3927 | this._positionHuds(); 3928 | 3929 | // Add controls. 3930 | this._controls.cursors = this.input.keyboard.createCursorKeys(); 3931 | this._controls.wasd = this.input.keyboard.addKeys(Supercold.controls.WASD); 3932 | this._controls.fireKey = this.input.keyboard.addKey(Supercold.controls.fireKey); 3933 | this._controls.dodgeKey = this.input.keyboard.addKey(Supercold.controls.dodgeKey); 3934 | this.input.keyboard.addKey(Supercold.controls.quitKey) 3935 | .onDown.addOnce(this.quit, this); 3936 | this.input.keyboard.addKey(Supercold.controls.restartKey) 3937 | .onDown.addOnce(this.restart, this); 3938 | 3939 | // The player should render above all other game entities (except text and UI). 3940 | player.bringToTop(); 3941 | // Calling bringToTop after enabling debugging hides the debug body. 3942 | // http://phaser.io/docs/2.6.2/Phaser.Physics.P2.BodyDebug.html 3943 | if (DEBUG) this.world.bringToTop(player.body.debugBody); 3944 | 3945 | // Spawn the first bot immediately. 3946 | this._spawnBot(true); 3947 | // Decide when to spawn the 2nd bot. It will be spawned faster than the rest. 3948 | this._next.botTime = this.rnd.realInRange(0.05, 0.10); 3949 | this._next.hotSwitch = this._hotswitchTimeout; 3950 | this._hotswitching = false; 3951 | 3952 | // We need to handle the shaking of the bullet bar separately to avoid 3953 | // shaking immediately after a bullet is fired and to allow the player 3954 | // to keep the fire control pressed without constanlty shaking the bar. 3955 | function onFireControl(buttonOrKey) { 3956 | /*jshint validthis: true */ 3957 | if (!buttonOrKey.shiftKey) { // not trying to hotswitch 3958 | if (player.remainingTime > 0) { 3959 | this._huds.bulletBar.shake(); 3960 | } 3961 | } 3962 | } 3963 | this.input.activePointer.leftButton.onDown.add(onFireControl, this); 3964 | this._controls.fireKey.onDown.add(onFireControl, this); 3965 | 3966 | this._announce(); 3967 | }; 3968 | 3969 | 3970 | Supercold.Game.prototype._getSpeed = function( 3971 | playerAlive, playerMoved, playerRotated, speeds) { 3972 | if (!playerAlive) return speeds.slow; 3973 | if (playerMoved) return speeds.normal; 3974 | if (this._mutators.freezetime) return 0; 3975 | if (this._mutators.fasttime) return speeds.slow; 3976 | if (playerRotated) return speeds.slower; 3977 | return speeds.slowest; 3978 | }; 3979 | 3980 | Supercold.Game.prototype._firePlayerBullet = function() { 3981 | var player = this._sprites.player; 3982 | 3983 | // Not ready to fire yet or no bullets left. 3984 | if (player.remainingTime > 0 || this._counts.bullets === 0) { 3985 | return false; 3986 | } 3987 | player.fire(); 3988 | --this._counts.bullets; 3989 | if (this._huds.bulletCount) { 3990 | this._huds.bulletCount.text = 3991 | Supercold.texts.BULLETS + this._counts.bullets; 3992 | } 3993 | return true; 3994 | }; 3995 | 3996 | 3997 | Supercold.Game.prototype.update = function() { 3998 | var player = this._sprites.player, 3999 | wasd = this._controls.wasd, 4000 | cursors = this._controls.cursors, 4001 | fireKey = this._controls.fireKey, 4002 | dodgeKey = this._controls.dodgeKey, 4003 | verVec = this._cached.verVec, 4004 | horVec = this._cached.horVec, 4005 | fireButton = this.input.activePointer.leftButton, 4006 | playerFired = false, 4007 | playerMoved, playerRotated, newDirection, 4008 | bulletSpeed, botSpeed, elapsedTime; 4009 | 4010 | if (this.superhot) { 4011 | return; 4012 | } 4013 | 4014 | // Angular velocity may change due to collisions, so set it to zero. 4015 | player.body.setZeroRotation(); 4016 | 4017 | // Process movement controls. 4018 | // Find where the player is heading, by calculating the 4019 | // angle between the horizontal and the vertical vector. 4020 | verVec.y = horVec.x = 0; // The other axes are never changed. 4021 | if (wasd.up.isDown || cursors.up.isDown) { 4022 | verVec.y = 1; 4023 | } else if (wasd.down.isDown || cursors.down.isDown) { 4024 | verVec.y = -1; 4025 | } 4026 | if (wasd.left.isDown || cursors.left.isDown) { 4027 | horVec.x = -1; 4028 | } else if (wasd.right.isDown || cursors.right.isDown) { 4029 | horVec.x = 1; 4030 | } 4031 | playerMoved = (verVec.y !== 0 || horVec.x !== 0) || player.dodging; 4032 | playerRotated = player.rotate() && !this._mutators.freelook; 4033 | 4034 | bulletSpeed = this._getSpeed( 4035 | player.alive, playerMoved, playerRotated, Supercold.speeds.bullet); 4036 | botSpeed = this._getSpeed( 4037 | player.alive, playerMoved, playerRotated, Supercold.speeds.bot); 4038 | 4039 | // When time slows down, distort the elapsed time proportionally. 4040 | elapsedTime = this._elapsedTime = this.time.physicsElapsed * 4041 | (bulletSpeed / Supercold.speeds.bullet.normal); 4042 | 4043 | if (player.alive) { 4044 | // Process firing controls (check that we are not trying to hotswitch). 4045 | if ((fireButton.isDown && !fireButton.shiftKey) || fireKey.isDown) { 4046 | playerFired = this._firePlayerBullet(); 4047 | } 4048 | newDirection = this.physics.arcade.angleBetween(verVec, horVec); 4049 | if (playerMoved && this._mutators.dodge && dodgeKey.isDown) { 4050 | player.dodge(newDirection); 4051 | } 4052 | player.advance(playerMoved, newDirection, elapsedTime); 4053 | } 4054 | 4055 | if (!this._hotswitching) { 4056 | this._next.hotSwitch -= elapsedTime; 4057 | } 4058 | // Check if there are any more bots to spawn. 4059 | if (this._counts.bots > 0) { 4060 | this._next.botTime -= elapsedTime; 4061 | if (this._next.botTime <= 0) { 4062 | this._spawnBot(); 4063 | this._next.botTime = this.rnd.realInRange( 4064 | Math.max(1.2 - 0.010*this.level, 0.5), 4065 | Math.max(1.8 - 0.015*this.level, 0.5)); 4066 | } 4067 | } 4068 | 4069 | // Update bots. 4070 | this._groups.bots.advance( 4071 | elapsedTime, botSpeed, this.level, player, playerFired); 4072 | // Update bullets. 4073 | this._groups.playerBullets.advance(bulletSpeed); 4074 | this._groups.botBullets.advance(bulletSpeed); 4075 | // Update HUDs (except minimap). 4076 | if (player.alive) { 4077 | this._huds.bulletBar.update( 4078 | Math.min(1, 1 - player.remainingTime / player.weapon.fireRate)); 4079 | this._huds.hotswitchBar.update( 4080 | Math.min(1, 1 - this._next.hotSwitch/this._hotswitchTimeout)); 4081 | } 4082 | }; 4083 | 4084 | /** 4085 | * The preRender method is called after all Game Objects have been updated, 4086 | * but before any rendering takes place. So, use it for calculations that 4087 | * need all physics computations updated. 4088 | */ 4089 | Supercold.Game.prototype.preRender = function() { 4090 | this._bulletTrailPainter.updateTrails(this._elapsedTime); 4091 | // Update the minimap here, now that bots are updated. 4092 | this._huds.minimap.update(this._sprites.player, this._groups.bots); 4093 | // Scroll the background appropriately. 4094 | this._scrollBackground(); 4095 | }; 4096 | 4097 | 4098 | /** 4099 | * Handles browser resizing. Called automatically by our Phaser game. 4100 | * 4101 | * @param {number} width - the new width 4102 | * @param {number} height - the new height 4103 | * @override 4104 | */ 4105 | Supercold.Game.prototype.resize = function(width, height) { 4106 | var scale = this.world.scale; 4107 | 4108 | Supercold._BaseState.prototype.resize.call(this, width, height); 4109 | 4110 | // Resize all sprites to account for the new scale of our world. 4111 | this._sprites.player.resize(scale); 4112 | 4113 | this._groups.bots.resize(scale); 4114 | this._groups.playerBullets.resize(scale); 4115 | this._groups.botBullets.resize(scale); 4116 | 4117 | this._bulletTrailPainter.resize(); 4118 | 4119 | this._huds.minimap.resize(0, 0); 4120 | this._huds.hotswitchBar.resize(0, 0); 4121 | this._huds.bulletBar.resize(0, 0); 4122 | if (this._huds.bulletCount) { 4123 | this._huds.bulletCount.fontSize = 4124 | this.getHudFontSize(Supercold.wordStyles.BULLETS.fontSize); 4125 | } 4126 | this._positionHuds(); 4127 | 4128 | this._announcer.resize(); 4129 | 4130 | this.rescale(this._sprites.background); 4131 | if (this._overlay) { 4132 | this.rescale(this._overlay); 4133 | } 4134 | }; 4135 | 4136 | 4137 | /** 4138 | * Performs final cleanup. Called automatically by our Phaser game. 4139 | */ 4140 | Supercold.Game.prototype.shutdown = function() { 4141 | /* 4142 | * Reminder: 4143 | * Because BitmapData's are now Game Objects themselves, and don't live on 4144 | * the display list, they are NOT automatically cleared when we change 4145 | * State. Therefore we must call BitmapData.destroy in our State's 4146 | * shutdown method if we wish to free-up the resources they use. 4147 | * Note that BitmapData objects added to the cache will be destroyed for us. 4148 | */ 4149 | if (DEBUG) log('Shutting down Game state...'); 4150 | 4151 | // The UI group is added directly to the stage and needs manual removal. 4152 | this.stage.removeChild(this._groups.ui); 4153 | 4154 | // Captured keys may mess with input on main menu. 4155 | this.input.keyboard.clearCaptures(); 4156 | }; 4157 | 4158 | 4159 | /** 4160 | * Used here for debugging purposes. 4161 | */ 4162 | Supercold.Game.prototype.render = function() { 4163 | if (DEBUG) { 4164 | this.showDebugInfo(this._sprites.player); 4165 | } 4166 | }; 4167 | 4168 | /****************************** Setup and Expose ******************************/ 4169 | 4170 | function hideMenu() { 4171 | document.getElementById('menu').style.display = 'none'; 4172 | } 4173 | function showMenu() { 4174 | document.getElementById('menu').style.display = 'block'; 4175 | } 4176 | 4177 | function loadLevelInfo() { 4178 | document.getElementById('maxlevel').textContent = Supercold.storage.loadHighestLevel(); 4179 | document.getElementById('level').value = Supercold.storage.loadLevel(); 4180 | } 4181 | 4182 | /** 4183 | * Marks the checked property of the each input 4184 | * according to the properties of the given object. 4185 | */ 4186 | function checkInputs(inputs) { 4187 | var input, element; 4188 | 4189 | for (input in inputs) { 4190 | if (inputs.hasOwnProperty(input)) { 4191 | element = document.getElementById(input); 4192 | // Guard against old properties from previous versions! 4193 | if (element) { 4194 | element.checked = inputs[input]; 4195 | } 4196 | } 4197 | } 4198 | } 4199 | 4200 | function unlock(level) { 4201 | Array.prototype.forEach.call( 4202 | document.querySelectorAll('.locked' + level), function(el) { 4203 | el.className = ''; 4204 | el.previousElementSibling.removeAttribute('disabled'); 4205 | }); 4206 | Array.prototype.forEach.call( 4207 | document.querySelectorAll('.disabled' + level), function(el) { 4208 | el.previousElementSibling.removeAttribute('disabled'); 4209 | }); 4210 | } 4211 | 4212 | function showUnlocked(unlockLevels, what) { 4213 | var level = Supercold.storage.loadHighestLevel(), i; 4214 | 4215 | for (i = 0; i < unlockLevels.length; ++i) { 4216 | if (unlockLevels[i] <= level) { 4217 | unlock(unlockLevels[i]); 4218 | } else { 4219 | break; 4220 | } 4221 | } 4222 | if (level >= unlockLevels[unlockLevels.length-1]) { 4223 | document.getElementById(what + '-hint').style.display = 'none'; 4224 | } else { 4225 | document.getElementById(what + '-hint-level').textContent = unlockLevels[i]; 4226 | } 4227 | } 4228 | 4229 | function showUnlockedMutators() { 4230 | showUnlocked([20, 30, 40, 50, 70, 80, 90, 100, 128], 'mutator'); 4231 | } 4232 | 4233 | function showUnlockedGuns() { 4234 | showUnlocked([10, 20, 30, 40, 50, 75], 'gun'); 4235 | } 4236 | 4237 | Supercold.play = function play(parent) { 4238 | // Tell Phaser to cover the entire window and use the CANVAS renderer. 4239 | // Note that WebGL has issues on some platforms, so we go for plain canvas! 4240 | var game = new Phaser.Game('100', '100', Phaser.CANVAS, parent), 4241 | mutators, guns, previous; 4242 | 4243 | warn('WebGL has performance issues on some platforms! Using canvas renderer...\n\n'); 4244 | 4245 | /** 4246 | * The single instance of data storage for our game. 4247 | */ 4248 | Supercold.storage = new Supercold.GameStorageManager(); 4249 | 4250 | loadLevelInfo(); 4251 | document.getElementById('level').addEventListener('change', function(event) { 4252 | var level = Supercold.storage.loadHighestLevel(), 4253 | newLevel = parseInt(this.value, 10); 4254 | 4255 | this.value = (isNaN(newLevel)) ? level : Math.min(level, Math.max(1, newLevel)); 4256 | Supercold.storage.saveLevel(this.value); 4257 | }, false); 4258 | 4259 | mutators = Supercold.storage.loadMutators(); 4260 | if (mutators === null) { 4261 | // Default mutators (all off) 4262 | Supercold.storage.saveMutators({ 4263 | freelook: false, 4264 | fasttime: false, 4265 | fastgun: false, 4266 | bighead: false, 4267 | chibi: false, 4268 | doge: false, 4269 | dodge: false, 4270 | lmtdbull: false, 4271 | superhotswitch: false, 4272 | secondchance: false, 4273 | freezetime: false, 4274 | godmode: false 4275 | }); 4276 | } 4277 | checkInputs(mutators); 4278 | 4279 | // We handle gun storage the same way as mutator storage, even though they 4280 | // work differently in the game. It may help with changes in the future. 4281 | guns = Supercold.storage.loadGuns(); 4282 | if (guns === null) { 4283 | // Default gun (pistol) 4284 | Supercold.storage.saveGuns({ 4285 | pistol: true, 4286 | burst: false, 4287 | burst3: false, 4288 | blunderbuss: false, 4289 | shotgun: false, 4290 | dbshotgun: false, 4291 | rifle: false 4292 | }); 4293 | } 4294 | checkInputs(guns); 4295 | previous = document.querySelector('#guns input:checked').id; 4296 | 4297 | showUnlockedMutators(); 4298 | showUnlockedGuns(); 4299 | 4300 | // Handle mutator modifications. 4301 | Array.prototype.forEach.call( 4302 | document.querySelectorAll('#mutators input'), function(input) { 4303 | input.addEventListener('change', function(event) { 4304 | var mutators = Supercold.storage.loadMutators(); 4305 | 4306 | mutators[this.id] = this.checked; 4307 | Supercold.storage.saveMutators(mutators); 4308 | }, false); 4309 | }); 4310 | // Handle gun modifications. 4311 | Array.prototype.forEach.call( 4312 | document.querySelectorAll('#guns input'), function(input) { 4313 | input.addEventListener('change', function(event) { 4314 | var guns = Supercold.storage.loadGuns(); 4315 | 4316 | // Disable previously selected weapon. 4317 | guns[previous] = false; 4318 | guns[this.id] = this.checked; 4319 | Supercold.storage.saveGuns(guns); 4320 | previous = this.id; 4321 | }, false); 4322 | }); 4323 | 4324 | // Global per-instance options. Use a namespace to avoid name clashes. 4325 | game.supercold = { 4326 | onMainMenuOpen: function() { 4327 | loadLevelInfo(); 4328 | showUnlockedMutators(); 4329 | showUnlockedGuns(); 4330 | showMenu(); 4331 | } 4332 | }; 4333 | 4334 | game.state.add('Boot', Supercold.Boot); 4335 | game.state.add('Preloader', Supercold.Preloader); 4336 | game.state.add('MainMenu', Supercold.MainMenu); 4337 | game.state.add('Intro', Supercold.Intro); 4338 | game.state.add('Game', Supercold.Game); 4339 | 4340 | game.state.start('Boot'); 4341 | 4342 | document.getElementById('start').addEventListener('click', function(event) { 4343 | hideMenu(); 4344 | game.state.start('Intro'); 4345 | }, false); 4346 | }; 4347 | 4348 | 4349 | window.Supercold = Supercold; 4350 | 4351 | }(window, Phaser)); 4352 | -------------------------------------------------------------------------------- /privacy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | Privacy Policy - SUPERCOLD 27 | 28 | 29 | 38 | 39 | 40 |

41 | This Privacy Policy governs the manner in which SUPERCOLD ("we", "us") collects, 42 | uses, maintains and discloses information collected from users (each, a "User") 43 | of the SUPERCOLD website ("Site"). This privacy policy applies to the Site and 44 | all products and services offered by SUPERCOLD. 45 |

46 |

Personal identification information

47 |

48 | We may collect personal identification information from Users in a variety 49 | of ways in connection with activities, services, features or resources we 50 | make available on our Site. Users may visit our Site anonymously. We will 51 | collect personal identification information from Users only if they voluntarily 52 | submit such information to us. Users can always refuse to supply personally 53 | identification information, except that it may prevent them from engaging in 54 | certain Site related activities. 55 |

56 |

Non-personal identification information

57 |

58 | We may collect non-personal identification information about Users whenever 59 | they interact with our Site. Non-personal identification information may 60 | include the browser name, the type of computer and technical information 61 | about Users means of connection to our Site, such as the operating system 62 | and the Internet service providers utilized and other similar information. 63 |

64 |

Web browser cookies

65 |

66 | Our Site may use "cookies" to enhance User experience. User's web browser 67 | places cookies on their hard drive for record-keeping purposes and sometimes 68 | to track information about them. User may choose to set their web browser to 69 | refuse cookies, or to alert you when cookies are being sent. If they do so, 70 | note that some parts of the Site may not function properly. 71 |

72 |

How we use collected information

73 |

74 | We may use feedback you provide to improve our products and services. 75 |

76 |

How we protect your information

77 |

78 | We adopt appropriate data collection, storage and processing practices and 79 | security measures to protect against unauthorized access, alteration, 80 | disclosure or destruction of your personal information, username, 81 | password, transaction information and data stored on our Site. 82 |

83 |

Sharing your personal information

84 |

85 | We do not sell, trade or rent Users personal identification information to 86 | others. We may publicly share generic (anonymized) aggregated demographic 87 | information not linked to any personal identification information regarding 88 | visitors and users. 89 |

90 |

Google Analytics

91 |

92 | We use Google Analytics services to collect and process non-personal 93 | identification information about Users whenever they interact with 94 | our Site (as outlined above). 95 |

96 |

97 | You may opt-out of Google Analytics services by following the instructions 98 | described at the 99 | Google Analytics Help Center. 100 |

101 |

Advertising

102 |

103 | Ads appearing on our site may be delivered to Users by advertising 104 | partners, who may set cookies. These cookies allow the ad server to 105 | recognize your computer each time they send you an online advertisement 106 | to compile non personal identification information about you or others 107 | who use your computer. This information allows ad networks to, among 108 | other things, deliver targeted advertisements that they believe will 109 | be of most interest to you. This privacy policy does not cover the 110 | use of cookies by any advertisers. 111 |

112 | 131 |

Changes to this privacy policy

132 |

133 | We have the discretion to update this privacy policy at any time. When 134 | we do, we will revise the updated date at the bottom of this page. We 135 | encourage Users to frequently check this page for any changes to stay 136 | informed about how we are helping to protect the personal information 137 | we collect. You acknowledge and agree that it is your responsibility 138 | to review this privacy policy periodically and become aware of 139 | modifications. 140 |

141 |

Your acceptance of these terms

142 |

143 | By using this Site, you signify your acceptance of this policy. If you 144 | do not agree to this policy, please do not use our Site. Your continued 145 | use of the Site following the posting of changes to this policy will be 146 | deemed your acceptance of those changes. 147 |

148 |

149 | This document was last updated on April 27, 2017. 150 |

151 | 152 | 153 | -------------------------------------------------------------------------------- /terms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | Terms of Use - SUPERCOLD 27 | 28 | 29 | 38 | 39 | 40 |

Definitions

41 |

42 | Site: means the URL at https://import-this.github.io/supercold/
43 | SUPERCOLD Game: means the online game entitled SUPERCOLD, as available 44 | on the Site and includes all related games, software, applications, 45 | products or services relating to the SUPERCOLD Game
46 | We, our or us: means SUPERCOLD
47 | You: means the actual or potential user of the Site and all current or 48 | future SUPERCOLD games 49 |

50 | 51 |

52 | These Terms and Conditions govern the content and your use of the Site 53 | and/or SUPERCOLD Game and the resulting service that we may provide to you. 54 | Your use of the Site and/or the SUPERCOLD Game (which includes viewing or 55 | accessing them in any way) signifies your acceptance of these Terms and 56 | Conditions detailed below and constitutes a legally binding acceptance 57 | of this agreement. If you do not agree to these Terms and Conditions 58 | you must not access or use our Site and/or the SUPERCOLD Game and you 59 | should refrain from doing so unless you consent to these Terms and Conditions. 60 | A minor (being anyone under the age of 13) should seek consent from his or her 61 | legal guardian before using the Site and/or SUPERCOLD Game. 62 |

63 | 64 |

Changes to terms

65 |

66 | We reserve the right to modify, alter and update the content of these Terms and Conditions at any time. 67 | It is your responsibility to check these Terms and Conditions regularly so that you are aware of any such changes. 68 | Your continuing use of the Site constitutes your agreement to the changes. 69 | The current terms and conditions will be posted here: https://import-this.github.io/supercold/terms.html. 70 |

71 |

Changes to the Site

72 |

73 | We reserve the right to change or alter the content of the Site and/or the SUPERCOLD Game at any time. 74 |

75 |

Age restrictions

76 |

77 | We do not target the SUPERCOLD website or its services to users under 13 of age. 78 | We may place certain access restrictions in relation to users under 13 years of age. 79 | You agree that if you assist users under 13 years old to access the SUPERCOLD website 80 | or services, with your computer, internet enabled device, internet connection and/or 81 | facilities (whether owned, leased or borrowed) that you will assume full responsibility 82 | and liability for any consequences and that UNDER NO CIRCUMSTANCES, TO THE EXTENT 83 | PERMITTED BY LAW, WILL NEITHER SUPERCOLD, ANY THIRD PARTY CONTENT PROVIDER NOR THEIR 84 | RESPECTIVE AGENTS SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL OR 85 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THE SITE BY USERS 86 | UNDER 13 YEARS OF AGE, EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 87 | DAMAGES. 88 |

89 |

Links

90 |

91 | We accept no responsibility for third party links to or from the Site and/or SUPERCOLD Game. 92 | The inclusion of these links does not mean that we endorse the material or content of the sites. 93 |

94 |

Availability

95 |

96 | There may be times when the Site and/or SUPERCOLD Game are unavailable due to maintenance of 97 | the system or technical problems. We may also suspend or permanently remove the Site and/or 98 | any SUPERCOLD Game for any reason without needing your consent or giving prior notice. 99 |

100 |

Viruses

101 |

102 | We do not make any warranty that the website is free from infection from viruses; 103 | nor does any provider of content to the site or their respective agents make any 104 | warranty as to the results to be obtained from use of the site. We will not be 105 | liable for any loss, damage or upset that you suffer as a consequence of using 106 | the Site and/or SUPERCOLD Game. 107 | We will not be liable for any loss, damage, corruption of data or upset that you 108 | suffer as a consequence of receiving a virus or other malicious software through 109 | use of the Site. 110 |

111 |

Advertisements

112 |

113 | If you click on any advert, you will be dealing with external companies responsible for that advert. 114 | We do not control the actions of these companies or the content of their websites. 115 | We accept no responsibility for any third party advertisements that may appear on the Site and/or SUPERCOLD Game. 116 | We will not be liable for any loss, damage or upset which you suffer as a result 117 | of viewing or clicking on advertisements in any circumstance. Any claim for loss 118 | should be directed against the external company responsible for the advert. 119 |

120 | 128 |

Limitation of Liability

129 |

130 | 131 | Although we attempt to ensure that all information contained on this 132 | website is error-free, we accept no liability for any omissions. We 133 | will not be liable for any loss, damage or upset that you suffer as 134 | a consequence of the Site becoming temporarily or permanently unavailable. 135 | UNDER NO CIRCUMSTANCES SHALL SUPERCOLD OR ITS RESPECTIVE AGENTS BE LIABLE TO 136 | THE EXTENT PERMITTED BY LAW FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL OR 137 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF OR INABILITY TO USE THE SUPERCOLD 138 | WEB SITE, SOFTWARE AND/OR SERVICES, EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE 139 | POSSIBILITY OF SUCH DAMAGES. YOU AGREE TO ASSUME ALL RISK RELATED TO YOUR USE 140 | OF THE SITE AND GAME, INCLUDING BUT NOT LIMITED TO, THE RISK OF COMMUNICATIONS 141 | WITH OTHER PEOPLE OR DAMAGE TO YOUR COMPUTER. 142 | 143 |

144 |

145 | This document was last updated on April 27, 2017. 146 |

147 | 148 | 149 | --------------------------------------------------------------------------------