,
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 |
--------------------------------------------------------------------------------