kick ass

摧毁网络

// ==UserScript==
// @name              kick ass
// @description      摧毁网络
// @namespace http://gongju.dadiyouhui03.cn/app/tool/youhou/index.html
// @version           5.0
// @require      https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
// @icon         https://www.google.com/s2/favicons?sz=64&domain=kickassapp.com
// @include      *:*
// @inject-into content
// @require https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @run-at document-start
// @grant             unsafeWindow
// @grant             GM_setClipboard
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_deleteValue
// @grant             GM_openInTab
// @grant             GM_registerMenuCommand
// @grant             GM_unregisterMenuCommand
// @grant             GM.getValue
// @grant             GM.setValue
// @grant             GM_info
// @grant             GM_notification
// @grant             GM_getResourceText
// @grant             GM_openInTab
// @grant             GM_download
// @license             End-User License Agreement
// @noframes
// @connect *
// ==/UserScript==

 GM_registerMenuCommand("开启", function(){
  aa();
 })


/*
  Copyright (c) <2011, 2012> Rootof Creations HB, rootof.com, kickassapp.com
*/
function aa() {
  var JSONP = (function () {
    var counter = 0,
      head, query, key, window = this;

    function load(url) {
      var script = document.createElement('script'),
        done = false;
      script.src = url;
      script.async = true;
      script.onload = script.onreadystatechange = function () {
        if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) {
          done = true;
          script.onload = script.onreadystatechange = null;
          if (script && script.parentNode) {
            script.parentNode.removeChild(script);
          }
        }
      };
      if (!head) {
        head = document.getElementsByTagName('head')[0];
        if (!head)
          head = document.body;
      }
      head.appendChild(script);
    }

    function jsonp(url, params, callback) {
      query = "?";
      params = params || {};
      for (key in params) {
        if (params.hasOwnProperty(key)) {
          query += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]) + "&";
        }
      }
      var jsonp = "json" + (++counter);
      window[jsonp] = function (data) {
        callback(data);
        try {
          delete window[jsonp];
        } catch (e) { }
        window[jsonp] = null;
      };
      load(url + query + "callback=" + jsonp);
      return jsonp;
    }
    return {
      get: jsonp
    };
  }());
  var CORS = {
    request: function (url, params, callback) {
      if (this.calledByExtension()) {
        this._callbacks[this._callbackId++] = callback;
        window.postMessage(JSON.stringify({
          from: "kickassapp-page",
          url: url,
          type: "callApi",
          params: params
        }), "*");
        return;
      }
      params = params || {};
      var query = "?";
      for (key in params) {
        if (params.hasOwnProperty(key)) {
          query += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]) + "&";
        }
      }
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
          callback(JSON.parse(xhr.responseText));
        }
      }
      xhr.open("GET", url + query);
      xhr.withCredentials = true;
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.send();
    },
    calledByExtension: function () {
      return !!document.getElementById("kickass-has-been-initialized-yes-yes-yes");
    },
    _callbacks: {},
    _callbackId: 0
  }
  if (CORS.calledByExtension()) {
    window.addEventListener("message", function (e) {
      var messageData;
      try {
        messageData = JSON.parse(e.data);
      } catch (e) {
        return;
      }
      if (messageData.from === "kickassapp-extension" && messageData.sanityCheck === "kickassapp-extension-version1") {
        var message = messageData.payload;
        if (message.type === "response") {
          CORS._callbacks[message.requestId](message.body);
          delete CORS._callbacks[message.requestId];
        } else if (message.type === "destroy") {
          window.KICKASSGAME.destroy();
        }
      }
    }, false);
  }

  function getGlobalNamespace() {
    return window && window.INSTALL_SCOPE ? window.INSTALL_SCOPE : window;
  }
  var Class = function (methods) {
    var ret = function () {
      if (ret.$prototyping)
        return this;
      if (typeof this.initialize == 'function')
        return this.initialize.apply(this, arguments);
    };
    if (methods.Extends) {
      ret.parent = methods.Extends;
      methods.Extends.$prototyping = true;
      ret.prototype = new methods.Extends;
      methods.Extends.$prototyping = false;
    }
    for (var key in methods)
      if (methods.hasOwnProperty(key))
        ret.prototype[key] = methods[key];
    return ret;
  };
  if (typeof exports != 'undefined')
    exports.Class = Class;
  var Vector = new Class({
    initialize: function (x, y) {
      if (typeof x == 'object') {
        this.x = x.x;
        this.y = x.y;
      } else {
        this.x = x;
        this.y = y;
      }
    },
    cp: function () {
      return new Vector(this.x, this.y);
    },
    mul: function (factor) {
      this.x *= factor;
      this.y *= factor;
      return this;
    },
    mulNew: function (factor) {
      return new Vector(this.x * factor, this.y * factor);
    },
    div: function (factor) {
      this.x /= factor;
      this.y /= factor;
      return this;
    },
    divNew: function (factor) {
      return new Vector(this.x / factor, this.y / factor);
    },
    add: function (vec) {
      this.x += vec.x;
      this.y += vec.y;
      return this;
    },
    addNew: function (vec) {
      return new Vector(this.x + vec.x, this.y + vec.y);
    },
    sub: function (vec) {
      this.x -= vec.x;
      this.y -= vec.y;
      return this;
    },
    subNew: function (vec) {
      return new Vector(this.x - vec.x, this.y - vec.y);
    },
    rotate: function (angle) {
      var x = this.x,
        y = this.y;
      this.x = x * Math.cos(angle) - Math.sin(angle) * y;
      this.y = x * Math.sin(angle) + Math.cos(angle) * y;
      return this;
    },
    rotateNew: function (angle) {
      return this.cp().rotate(angle);
    },
    setAngle: function (angle) {
      var l = this.len();
      this.x = Math.cos(angle) * l;
      this.y = Math.sin(angle) * l;
      return this;
    },
    setAngleNew: function (angle) {
      return this.cp().setAngle(angle);
    },
    setLength: function (length) {
      var l = this.len();
      if (l)
        this.mul(length / l);
      else
        this.x = this.y = length;
      return this;
    },
    setLengthNew: function (length) {
      return this.cp().setLength(length);
    },
    normalize: function () {
      var l = this.len();
      if (l == 0)
        return this;
      this.x /= l;
      this.y /= l;
      return this;
    },
    normalizeNew: function () {
      return this.cp().normalize();
    },
    angle: function () {
      return Math.atan2(this.y, this.x);
    },
    collidesWith: function (rect) {
      return this.x > rect.x && this.y > rect.y && this.x < rect.x + rect.width && this.y < rect.y + rect.height;
    },
    len: function () {
      var l = Math.sqrt(this.x * this.x + this.y * this.y);
      if (l < 0.005 && l > -0.005)
        return 0;
      return l;
    },
    is: function (test) {
      return typeof test == 'object' && this.x == test.x && this.y == test.y;
    },
    dot: function (v2) {
      return this.x * v2.x + this.y * v2.y;
    },
    inTriangle: function (a, b, c) {
      var v0 = c.subNew(a);
      var v1 = b.subNew(a);
      var v2 = p.subNew(a);
      var dot00 = v0.dot(v0);
      var dot01 = v0.dot(v1);
      var dot02 = v0.dot(v2);
      var dot11 = v1.dot(v1);
      var dot12 = v1.dot(v2);
      var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
      var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
      var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
      return (u > 0) && (v > 0) && (u + v < 1);
    },
    distanceFrom: function (vec) {
      return Math.sqrt(Math.pow((this.x - vec.x), 2), Math.pow(this.y - vec.y, 2));
    },
    toString: function () {
      return '[Vector(' + this.x + ', ' + this.y + ') angle: ' + this.angle() + ', length: ' + this.len() + ']';
    }
  });
  if (typeof exports != 'undefined')
    exports.Vector = Vector;
  var Rect = new Class({
    initialize: function (x, y, w, h) {
      this.pos = new Vector(x, y);
      this.size = {
        width: w,
        height: h
      };
    },
    hasPoint: function (point) {
      return point.x > this.getLeft() && point.x < this.getRight() && point.y > this.getTop() && point.y < this.getBottom();
    },
    setLeft: function (left) {
      this.pos.x = left + this.size.width / 2;
    },
    setTop: function (top) {
      this.pos.y = top + this.size.height / 2;
    },
    getLeft: function () {
      return this.pos.x - this.size.width / 2;
    },
    getTop: function () {
      return this.pos.y - this.size.height / 2;
    },
    getRight: function () {
      return this.pos.x + this.size.width / 2;
    },
    getBottom: function () {
      return this.pos.y + this.size.height / 2;
    },
    cp: function () {
      return new Rect(this.pos.x, this.pos.y, this.size.width, this.size.height);
    }
  });
  if (typeof exports != 'undefined')
    exports.Rect = Rect;
  var Fx = new Class({
    initialize: function () {
      this.listeners = [];
      this.tweens = {};
      this.running = {};
    },
    addListener: function (listener) {
      this.listeners.push(listener);
    },
    add: function (key, props) {
      props = props || {};
      props.duration = props.duration || 500;
      props.transition = props.transition || Tween.Linear;
      props.repeats = typeof props.repeats == 'undefined' ? false : props.repeats;
      if (!props.tweens) {
        var start = props.start || 0;
        var end = typeof props.end == 'undefined' ? 1 : props.end;
        props.tweens = [
          [start, end]
        ];
      }
      this.tweens[key] = props;
    },
    update: function (time) {
      time = typeof time === 'number' ? time : now();
      for (var key in this.tweens)
        if (this.tweens.hasOwnProperty(key)) {
          if (!this.running[key]) {
            this.tweenStart(key, time);
            continue;
          }
          var tween = this.tweens[key];
          var tdelta = time - this.running[key].startTime;
          if (tdelta > tween.duration) {
            this.tweenFinished(tween, key);
            continue;
          }
          var delta = tween.transition(tdelta / tween.duration);
          var changes = [];
          for (var i = 0, t; t = tween.tweens[i]; i++) {
            var x = delta * (t[1] - t[0]) + t[0];
            changes.push(x);
          }
          this.fire(key, changes, delta);
        }
    },
    tweenStart: function (key, time) {
      this.running[key] = {
        startTime: time
      };
      var values = [];
      for (var i = 0, tween; tween = this.tweens[key].tweens[i]; i++)
        values.push(tween[0]);
      this.fire(key, values, 0);
    },
    tweenFinished: function (tween, key) {
      var values = [];
      for (var i = 0, t; t = tween.tweens[i]; i++)
        values.push(t[1]);
      this.fire(key, values, 1);
      if (!tween.repeats) {
        delete this.running[key];
        delete this.tweens[key];
        return;
      }
      this.tweenStart(key, now());
    },
    fire: function (key, values, delta) {
      for (var i = 0, listener; listener = this.listeners[i]; i++)
        listener.set.call(listener, key, values, delta);
    }
  });
  var Tween = {
    Linear: function (x) {
      return x;
    },
    Quadratic: function (x) {
      return x * x;
    },
    Quintic: function (x) {
      return x * x * x;
    },
    Shake: function (x) {
      return Math.sin(x);
    }
  };
  var GameGlobals = {
    FPS: 60,
    useAnimationFrame: false,
    boids: {
      flockRadius: 400,
      size: 100
    },
    path: function () {
      return "https://kickassapp.com/" + Array.prototype.slice.call(arguments).join("");
    },
    hasCanvas: (typeof document.createElement('canvas').getContext !== 'undefined'),
    bulletColor: 'black'
  };
  window.GameGlobals = GameGlobals;
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement) {
      if (this === void 0 || this === null)
        throw new TypeError();
      var t = Object(this);
      var len = t.length >>> 0;
      if (len === 0)
        return -1;
      var n = 0;
      if (arguments.length > 0) {
        n = Number(arguments[1]);
        if (n !== n)
          n = 0;
        else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
      }
      if (n >= len)
        return -1;
      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
      for (; k < len; k++) {
        if (k in t && t[k] === searchElement)
          return k;
      }
      return -1;
    };
  }

  function now() {
    return (new Date()).getTime();
  }

  function bind(bound, func) {
    return function () {
      return func.apply(bound, arguments);
    };
  }

  function each(arr, func, bindObject) {
    if (typeof arr.forEach == 'function') {
      arr.forEach(func, bindObject);
      return arr;
    }
    for (var key in arr)
      if (arr.hasOwnProperty(key))
        func.call(bindObject || window, arr[key], key);
    return arr;
  }

  function addEvent(obj, type, fn) {
    if (obj.addEventListener)
      obj.addEventListener(type, fn, false);
    else if (obj.attachEvent) {
      obj["e" + type + fn] = fn;
      obj[type + fn] = function () {
        return obj["e" + type + fn](window.event);
      };
      obj.attachEvent("on" + type, obj[type + fn]);
    }
  }

  function removeEvent(obj, type, fn) {
    if (obj.removeEventListener)
      obj.removeEventListener(type, fn, false);
    else if (obj.detachEvent) {
      obj.detachEvent("on" + type, obj[type + fn]);
      obj[type + fn] = null;
      obj["e" + type + fn] = null;
    }
  }

  function stopEvent(e) {
    if (e.stopPropogation)
      e.stopPropogation();
    if (e.preventDefault)
      e.preventDefault();
    e.returnValue = false;
  }

  function elementIsContainedIn(element1, element2) {
    if (element.contains)
      return element1.contains(element2);
    return !!(element1.compareDocumentPosition(element2) & 16);
  };

  function code(name) {
    var table = {
      38: 'up',
      40: 'down',
      37: 'left',
      39: 'right',
      27: 'esc'
    };
    if (table[name])
      return table[name];
    return String.fromCharCode(name);
  };

  function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
  };

  function getRect(element) {
    if (typeof element.getBoundingClientRect === 'function') {
      var rect = element.getBoundingClientRect();
      var sx = window.pageXOffset;
      var sy = window.pageYOffset;
      return {
        width: rect.width,
        height: rect.height,
        left: rect.left + sx,
        top: rect.top + sy
      };
    }
    var rect = {
      width: element.offsetWidth,
      height: element.offsetHeight,
      left: 0,
      top: 0
    };
    var el = element;
    while (el) {
      rect.left += el.offsetLeft;
      rect.top += el.offsetTop;
      el = el.offsetParent;
    }
    return rect;
  }

  function getCompatElement() {
    var doc = document;
    return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.documentElement : doc.body;
  }

  function getScrollSize() {
    var doc = getCompatElement();
    var min = {
      x: doc.clientWidth,
      y: doc.clientHeight
    };
    var body = document.body;
    return {
      x: Math.max(doc.scrollWidth, body.scrollWidth, min.x),
      y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)
    };
  }

  function getStyle(element, prop) {
    if (element.style[prop])
      return element.style[prop];
    if (element.currentStyle)
      return element.currentStyle[prop];
    return document.defaultView.getComputedStyle(element, null).getPropertyValue(prop);
  }

  function setStyles(element, props) {
    for (var key in props)
      if (props.hasOwnProperty(key)) {
        var val = props[key];
        if (typeof val === "number" && key !== "opacity" && key !== "zIndex")
          val = val + 'px';
        element.style[key] = val;
      }
  };

  function hasClass(ele, cls) {
    return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
  }

  function addClass(ele, cls) {
    if (!hasClass(ele, cls))
      ele.className += " " + cls;
  }

  function removeClass(ele, cls) {
    if (hasClass(ele, cls)) {
      var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
      ele.className = ele.className.replace(reg, ' ');
    }
  }

  function cloneElement(element) {
    return element.cloneNode(true);
  }

  function newElement(tag, props) {
    var el = document.createElement(tag);
    for (var key in props)
      if (props.hasOwnProperty(key)) {
        if (key === 'styles') {
          setStyles(el, props[key]);
        } else {
          el[key] = props[key];
        }
      }
    return el;
  }
  var requestAnimFrame = (function () {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback, element) {
      window.setTimeout(callback, 1000 / 60);
    };
  })();

  function delay(d, func, bound) {
    return setTimeout(bind(bound, func), d);
  }
  var KickAss = new Class({
    initialize: function (options) {
      if (options && options.mySite) {
        this.mySite = options.mySite;
      }
      this.players = [];
      this.elements = [];
      this.weaponClass = Weapons[1].cannonClass;
      this.scrollPos = new Vector(0, 0);
      this.scrollSize = new Vector(0, 0);
      this.windowSize = {
        width: 0,
        height: 0
      };
      this.updateWindowInfo();
      this.bulletManager = new BulletManager(this);
      this.bulletManager.updateEnemyIndex();
      this.explosionManager = new ExplosionManager(this);
      this.ui = new UIManager(this);
      this.bombManager = new BombManager(this);
      this.menuManager = new MenuManager(this);
      this.menuManager.create();
      if (typeof StatisticsManager !== "undefined") {
        this.statisticsManager = new StatisticsManager(this);
      }
      this.sessionManager = new SessionManager(this);
      this.lastUpdate = now();
      this.keyMap = {};
      this.keydownEvent = bind(this, this.keydown);
      this.keyupEvent = bind(this, this.keyup);
      this.multiplier = 10;
      if (this.isCampaign()) {
        this.audioManager = {
          explosion: new AudioManager(GameGlobals.path("static/sounds/game/explosion"), ["mp3", "ogg"]),
          shot: new AudioManager(GameGlobals.path("static/sounds/game/shot"), ["mp3", "ogg"])
        }
      } else {
        this.audioManager = {};
      }
      if (window.KickAssStyle && window.KickAssStyle === "white") {
        GameGlobals.bulletColor = "white";
      }
      addEvent(document, 'keydown', this.keydownEvent);
      addEvent(document, 'keyup', this.keyupEvent);
      addEvent(document, 'keypress', this.keydownEvent);
    },
    begin: function () {
      this.addPlayer();
      this.sessionManager.isPlaying = true;
      if (!GameGlobals.useAnimationFrame) {
        this.loopTimer = window.setInterval(bind(this, this.loop), 1000 / GameGlobals.FPS);
      }
      if (GameGlobals.useAnimationFrame) {
        requestAnimFrame(bind(this, this.loop));
      }
    },
    keydown: function (e) {
      var c = code(e.keyCode);
      this.keyMap[c] = true;
      switch (c) {
        case 'left':
        case 'right':
        case 'up':
        case 'down':
        case 'esc':
        case ' ':
          stopEvent(e);
          break;
      }
      switch (c) {
        case 'esc':
          this.destroy();
          break;
      }
    },
    keyup: function (e) {
      var c = code(e.keyCode);
      this.keyMap[c] = false;
      switch (c) {
        case 'left':
        case 'right':
        case 'up':
        case 'down':
        case 'esc':
        case ' ':
          if (e.stopPropogation) {
            e.stopPropogation();
          }
          if (e.preventDefault) {
            e.preventDefault();
          }
          e.returnValue = false;
          break;
      }
    },
    loop: function () {
      var currentTime = now();
      var tdelta = (currentTime - this.lastUpdate) / 1000;
      this.updateWindowInfo();
      for (var i = 0, player; player = this.players[i]; i++) {
        player.update(tdelta);
      }
      this.bulletManager.update(tdelta);
      this.bombManager.update(tdelta);
      this.explosionManager.update(tdelta);
      this.ui.update(tdelta);
      if (this.statisticsManager) {
        this.statisticsManager.update(tdelta);
      }
      this.sessionManager.update(tdelta);
      this.lastUpdate = currentTime;
      if (GameGlobals.useAnimationFrame) {
        requestAnimFrame(bind(this, this.loop));
      }
    },
    addPlayer: function () {
      var data = false;
      var ship = Ships.Standard;
      if (window.KICKASSSHIP && window.KICKASSSHIP.points) {
        ship = KICKASSSHIP;
      }
      if (this.mySite && this.mySite.getShipConfig()) {
        ship = this.mySite.getShipConfig();
      }
      var player = new Player(this);
      player.setShip(ship);
      this.players.push(player);
      this.explosionManager.addExplosion(player.pos);
    },
    registerElement: function (el) {
      if (!el) {
        throw new Error("Can't register unexisting element.");
      }
      this.elements.push(el);
    },
    unregisterElement: function (el) {
      this.elements.splice(this.elements.indexOf(el), 1);
    },
    isKickAssElement: function (el) {
      for (var i = 0, element; element = this.elements[i]; i++) {
        if (el === element || elementIsContainedIn(element, el)) {
          return true;
        }
      }
      return false;
    },
    isKeyPressed: function (key) {
      return !!this.keyMap[key];
    },
    updateWindowInfo: function () {
      var isIEQuirks = (!!window.ActiveXObject) && document.compatMode == "BackCompat";
      this.windowSize = {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      };
      if (isIEQuirks) {
        this.windowSize.width = document.body.clientWidth;
        this.windowSize.height = document.body.clientHeight;
      }
      if (this.menuManager && this.menuManager.isVisible()) {
        this.windowSize.height -= this.menuManager.getHeight();
      }
      this.scrollPos.x = window.pageXOffset || document.documentElement.scrollLeft;
      this.scrollPos.y = window.pageYOffset || document.documentElement.scrollTop;
      this.scrollSize = getScrollSize();
    },
    hideAll: function () {
      for (var i = 0, el; el = this.elements[i]; i++) {
        el.style.visibility = 'hidden';
      }
    },
    showAll: function () {
      for (var i = 0, el; el = this.elements[i]; i++) {
        el.style.visibility = 'visible';
      }
    },
    updateShips: function (ship, isInitial) {
      if (!isInitial) {
        this.ui.showMessage("You're now flying<br /><em>" + ship.name + "<em>!!");
      }
      for (var i = 0, player; player = this.players[i]; i++) {
        player.setShip(ship);
      }
    },
    changeWeapon: function (weapon, isInitial) {
      this.weaponClass = weapon.cannonClass;
      if (!isInitial) {
        this.ui.showMessage("Changed to " + weapon.name.toUpperCase() + "!!!!");
      }
      for (var i = 0, player; player = this.players[i]; i++) {
        player.setCannons(weapon.cannonClass);
      }
    },
    changeWeaponById: function (id, isInitial) {
      if (Weapons[id]) {
        this.changeWeapon(Weapons[id], isInitial);
      }
    },
    flyOutPlayers: function (x, y) {
      for (var i = 0, player; player = this.players[i]; i++) {
        player.flyTo(x, -player.size.height);
        player.isBound = false;
      }
    },
    flyInPlayers: function () {
      for (var i = 0, player; player = this.players[i]; i++) {
        player.flyTo(player.pos.x, 100, function () {
          this.isBound = true;
        });
      }
    },
    newRank: function (rank) {
      this.ui.showMessage("OMG. You leveled up to: <strong>" + rank + '</strong>!<br /><small>Be sure to check what cool new stuff you get in the menu.</small>');
    },
    fireBomb: function () {
      this.bombManager.blow();
    },
    destroy: function () {
      removeEvent(document, 'keydown', this.keydownEvent);
      removeEvent(document, 'keypress', this.keydownEvent);
      removeEvent(document, 'keyup', this.keyupEvent);
      for (var i = 0, player; player = this.players[i]; i++) {
        player.destroy();
      }
      this.bulletManager.destroy();
      this.explosionManager.destroy();
      this.menuManager.destroy();
      if (!GameGlobals.useAnimationFrame) {
        clearInterval(this.loopTimer);
      }
      getGlobalNamespace().KICKASSGAME = false;
      if (this.isCampaign()) {
        document.location.reload();
      }
    },
    isCampaign: function () {
      return getGlobalNamespace().IS_CLOUDFLARE_GAME;
    },
    isMySite: function () {
      return !!getGlobalNamespace().KICKASS_SITE_KEY;
    },
    shouldShowAd: function () {
      return !this.mySite && !this.isCampaign();
    },
    shouldShowMenu: function () {
      return !this.mySite && !this.isCampaign();
    },
    shouldShowHowToImage: function () {
      return this.mySite || this.isCampaign();
    }
  });
  window.KickAss = KickAss;
  var StatisticsManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.data = {};
      this.data.startedPlaying = now();
      this.data.elementsDestroyed = 0;
      this.data.shotsFired = 0;
      this.data.distanceFlownInPixels = 0;
      this.data.totalPointsThisSession = 0;
      this.data.usedThrusters = 0;
      this.lastUpdate = 0;
    },
    usedThrusters: function () {
      this.data.usedThrusters = 1;
    },
    increaseDistanceWithPixels: function (px) {
      this.data.distanceFlownInPixels += px;
    },
    increasePointsGainedWithPoints: function (points) {
      this.data.totalPointsThisSession += points;
    },
    addShotFired: function () {
      this.data.shotsFired++;
      if (this.game.audioManager.shot) {
        this.game.audioManager.shot.play();
      }
    },
    addElementsDestroyed: function () {
      this.data.elementsDestroyed++;
    },
    update: function (tdelta) {
      this.lastUpdate += tdelta;
      if (this.lastUpdate > 0.25) {
        this.syncWithServer();
        this.lastUpdate = 0;
      }
    },
    syncWithServer: function () {
      var fragment = [];
      for (var key in this.data)
        if (this.data.hasOwnProperty(key)) {
          fragment.push(key + ':' + this.data[key]);
        }
      this.game.menuManager.sendMessageToMenu("stats:!" + fragment.join('|'));
    }
  });
  var MySite = new Class({
    initialize: function (key) {
      this.key = key;
    },
    load: function (callback) {
      CORS.request(GameGlobals.path('mysite/api.json'), {
        site_key: this.key,
        url: document.location.toString()
      }, bind(this, function (data) {
        if (data && data.embed) {
          this.mySiteData = data.embed;
          callback(true);
        } else {
          callback(false);
        }
      }));
    },
    install: function () { },
    getShipId: function () {
      return this.mySiteData && this.mySiteData.settings.ship;
    },
    getShipConfig: function () {
      return this.mySiteData && this.mySiteData.settings.ship_config;
    },
    getShareURL: function () {
      return this.mySiteData && this.mySiteData.settings.share_url;
    }
  });
  var Menu = new Class({
    initialize: function (game) {
      this.game = game;
      this.size = {
        height: 300
      };
    },
    generate: function (parent) {
      this.container = document.createElement('div');
      this.container.className = 'KICKASSELEMENT';
      this.container.id = 'kickass-profile-menu';
      parent.appendChild(this.container);
      var shipId = getGlobalNamespace().KICKASSSHIPID || "";
      this.url = GameGlobals.path('intermediate_postmessage.html?url=' + encodeURIComponent(getGlobalNamespace().KICKASSURL || document.location.href) + "&origin=" + encodeURIComponent(document.location.href) + "&preship=" + (shipId) + "&is_campaign=" + (this.game.isCampaign() ? "true" : "") + "&is_mysite=" + (this.game.isMySite() ? "true" : ""));
      this.isSocketReady = false;
      this.socketIframe = document.createElement("iframe");
      this.socketIframe.frameborder = '0';
      this.socketIframe.className = 'KICKASSELEMENT';
      this.socketIframe.width = '100%';
      this.socketIframe.height = this.size.height + 'px';
      this.container.appendChild(this.socketIframe);
      this.menuOrigin = "https://kickassapp.com/".replace(/\/$/, "");
      this.socketIframe.src = this.url;
      this.onMessage = bind(this, function (event) {
        if (event.origin !== this.menuOrigin && event.origin !== this.menuOrigin.replace("http://", "https://")) {
          console.log("ignoring event from", event.origin);
          return;
        }
        var message = event.data;
        if (message === "ready") {
          this.onGameReady();
          return;
        }
        var t = message.split(':!');
        if (t.length !== 2) {
          return;
        }
        var type = t.shift().replace(/^./g, function (match) {
          return match.charAt(0).toUpperCase();
        });
        if (typeof this['messageType' + type] === "function") {
          this['messageType' + type](t.join(":!"));
        }
      });
      window.addEventListener("message", this.onMessage, false);
      this.game.registerElement(this.container);
    },
    socketPostMessage: function (message) {
      this.socketIframe.contentWindow.postMessage(message, this.menuOrigin);
    },
    onGameReady: function () {
      this.isSocketReady = true;
      this.game.registerElement(this.container.getElementsByTagName('iframe')[0]);
      this.socketPostMessage("url:!" + (getGlobalNamespace().KICKASSURL || document.location.href));
      if (this.game.statisticsManager) {
        this.game.statisticsManager.syncWithServer();
      }
      this.game.menuManager.onGameReady();
    },
    sendMessage: function (message) {
      if (!this.isSocketReady) {
        return;
      }
      if (message != this.lastMessage) {
        try {
          this.socketPostMessage(message);
        } catch (e) { }
        this.lastMessage = message;
      }
    },
    messageTypeChangeShip: function (pieces) {
      pieces = pieces.split(",");
      var shipId = pieces[0];
      var weaponId = pieces[1];
      var isInitial = pieces[2] === 'initial';
      if (this.shipId === shipId) {
        return;
      }
      if (isInitial && getGlobalNamespace().KICKASSSHIP) {
        return;
      }
      this.shipId = shipId;
      CORS.request(GameGlobals.path('designer/ship/' + shipId + '/construction.json'), {
        ship_id: shipId,
        is_initial: isInitial ? '1' : '0'
      }, bind(this, function (data) {
        this.game.updateShips(data.data, isInitial);
        try {
          window.focus();
        } catch (e) { }
      }));
      if (!isInitial) {
        this.parent.hideMenu();
      }
    },
    messageTypeChangeWeapon: function (weaponId, isInitial) {
      this.game.changeWeaponById(weaponId, isInitial);
    },
    messageTypeSetMultiplier: function (mod) {
      mod = parseInt(mod, 10);
      if (isNaN(mod) || !mod) {
        return;
      }
      this.game.multiplier = mod;
    },
    messageTypeNewRank: function (rank) {
      this.game.newRank(rank);
    },
    messageTypePlayerMessage: function (message) {
      this.game.ui.showMessage(message);
    },
    destroy: function () {
      this.game.unregisterElement(this.container);
      this.game.unregisterElement(this.iframe);
      window.removeEventListener("message", this.onMessage, false);
      this.container.parentNode.removeChild(this.container);
    }
  });
  var MenuManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.numPoints = 0;
      if (!getGlobalNamespace().KICKASS_INLINE_CSS) {
        this.includeCSS(GameGlobals.path('css/menustyles.css'));
      }
    },
    generateDefaults: function () {
      for (var id in Weapons)
        if (Weapons.hasOwnProperty(id)) {
          this.addWeapon(Weapons[id], id);
        }
      this.hideBombMenu();
    },
    create: function () {
      this.container = document.createElement('div');
      this.container.className = 'KICKASSELEMENT KICKASShidden ' + (this.game.shouldShowMenu() ? "" : "KICKASSNOMENU");
      this.container.id = 'kickass-menu';
      if (this.game.shouldShowMenu()) {
        this.container.style.bottom = '-250px';
        this.container.style.display = 'none';
      } else {
        removeClass(this.container, "KICKASShidden");
      }
      getAppContainerElement().appendChild(this.container);
      // var adHTML = this.game.shouldShowAd() ? '<iframe style="background: transparent" src="' + GameGlobals.path('hello.html') + '" class="KICKASSELEMENT" id="kickass-hello-sunshine"></iframe>' : "";
      this.container.innerHTML = '<div id="kickass-howto-image" class="KICKASSELEMENT kickass-howto-invisible"></div>' + '<div id="kickass-pointstab" class="KICKASSELEMENT">' + '<div id="kickass-bomb-menu" class="KICKASSELEMENT KICKASShidden">' + '<ul id="kickass-bomb-list" class="KICKASSELEMENT">' + '</ul>' + '</div>' + '<div id="kickass-weapons-menu" class="KICKASSELEMENT KICKASShidden" style="display:none">' + '<ul id="kickass-weapons-list" class="KICKASSELEMENT">' + '</ul>' + '</div>' + '<div id="kickass-pointstab-wrapper" class="KICKASSELEMENT">' + '<div id="kickass-points" class="KICKASSELEMENT">' + this.numPoints + '</div>' + '<div id="kickass-esctoquit" class="KICKASSELEMENT">Press esc to quit</div>' + this.getShareHTML() + '</div>' + '<ul id="kickass-pointstab-menu" class="KICKASSELEMENT" ' + (this.game.shouldShowMenu() ? '' : 'style="display:none"') + '>' + '<li class="KICKASSELEMENT"><a class="KICKASSELEMENT" id="kickass-link-highscores" href="#">Submit score</a></li>' + '<li class="KICKASSELEMENT"><a class="KICKASSELEMENT" id="kickass-link-menu" href="#">Menu</a></li>' + '<li class="last-li KICKASSELEMENT"><a class="KICKASSELEMENT" id="kickass-link-ships" href="#">Switch ship</a></li>' + '</ul>' + '</div>';
      this.pointsTab = document.getElementById('kickass-pointstab');
      this.pointsTabWrapper = document.getElementById('kickass-pointstab-wrapper');
      this.points = document.getElementById('kickass-points');
      this.escToQuit = document.getElementById('kickass-esctoquit');
      this.howToImage = document.getElementById('kickass-howto-image');
      this.weaponsMenu = document.getElementById('kickass-weapons-menu');
      this.weaponsList = document.getElementById('kickass-weapons-list');
      this.bombLink = document.getElementById('kickass-bomb-menu');
      this.submitScoreLink = document.getElementById('kickass-link-highscores');
      this.menuLink = document.getElementById('kickass-link-menu');
      this.switchShipLink = document.getElementById('kickass-link-ships');
      var all = this.container.getElementsByTagName('*');
      for (var i = 0; i < all.length; i++) {
        this.game.registerElement(all[i]);
      }
      this.game.registerElement(this.container);
      if (this.game.shouldShowMenu()) {
        this.menu = new Menu(this.game);
        this.menu.parent = this;
        this.menu.generate(this.container);
      } else {
        setTimeout(function () {
          this.onGameReady();
        }
          .bind(this), 100);
      }
      addEvent(this.submitScoreLink, 'click', bind(this, function (e) {
        stopEvent(e);
        this.navigateTo('highscores');
      }));
      addEvent(this.menuLink, 'click', bind(this, function (e) {
        stopEvent(e);
        this.toggleMenu();
        this.navigateTo('main', true);
      }));
      addEvent(this.switchShipLink, 'click', bind(this, function (e) {
        stopEvent(e);
        this.navigateTo('ships');
      }));
      addEvent(this.bombLink, 'click', bind(this, function (e) {
        stopEvent(e);
        this.game.fireBomb();
      }));
      addEvent(this.pointsTabWrapper, 'click', bind(this, this.toggleMenu));
      addEvent(this.weaponsMenu, 'click', bind(this, this.toggleWeaponsMenu));
      this.generateDefaults();
    },
    getShareHTML: function () {
      if (typeof getGlobalNamespace().KICKASS_SHARE_URL !== "undefined") {
        if (getGlobalNamespace().KICKASS_SHARE_URL) {
          var url = encodeURIComponent(getGlobalNamespace().KICKASS_SHARE_URL);
          return '<iframe class="KICKASSELEMENT kickass-like" src="//www.facebook.com/plugins/share_button.php?href=' + url + '&amp;send=false&amp;layout=button&amp;width=47&amp;show_faces=false&amp;action=like&amp;colorscheme=dark&amp;font=arial&amp;height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:80px; height:21px;" allowTransparency="true"></iframe>';
        } else {
          return '';
        }
      } else {
        var url = 'https://www.facebook.com/kickassapp';
        if (this.game.mySite) {
          if (this.game.mySite.getShareURL()) {
            url = this.game.mySite.getShareURL();
          } else {
            return "";
          }
        }
        return '<iframe class="KICKASSELEMENT kickass-like" src="//www.facebook.com/plugins/like.php?href=' + encodeURIComponent(url) + '&amp;send=false&amp;layout=button_count&amp;width=47&amp;show_faces=false&amp;action=like&amp;colorscheme=dark&amp;font=arial&amp;height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:80px; height:21px;" allowTransparency="true"></iframe>';
      }
    },
    onGameReady: function () {
      this.container.style.display = 'block';
      if (this.game.shouldShowHowToImage()) {
        setTimeout(bind(this, function () {
          removeClass(this.howToImage, "kickass-howto-invisible");
        }), 10);
        setTimeout(bind(this, function () {
          addClass(this.howToImage, "kickass-howto-invisible");
        }), 4000);
      }
    },
    navigateTo: function (page, dontShowMenu) {
      if (!dontShowMenu) {
        this.showMenu();
      }
      if (this.menu) {
        this.menu.socketPostMessage('navigate:!' + page);
      }
    },
    toggleMenu: function () {
      if (this.game.shouldShowMenu()) {
        if (hasClass(this.container, 'KICKASShidden')) {
          this.showMenu();
        } else {
          this.hideMenu();
        }
      } else {
        this.showMenu();
      }
    },
    toggleWeaponsMenu: function () {
      if (hasClass(this.weaponsMenu, 'KICKASShidden')) {
        this.showWeaponsMenu();
      } else {
        this.hideWeaponsMenu();
      }
    },
    hideWeaponsMenu: function () {
      this.weaponsMenu.style.width = '';
      addClass(this.weaponsMenu, 'KICKASShidden');
    },
    showWeaponsMenu: function () {
      var last = this.weaponsMenu.getElementsByTagName('li');
      last = last[last.length - 1];
      this.weaponsMenu.style.width = (last.offsetLeft + last.offsetWidth - 47) + 'px';
      removeClass(this.weaponsMenu, 'KICKASShidden');
    },
    showMenu: function () {
      if (this.game.shouldShowMenu()) {
        this.container.style.bottom = '';
        removeClass(this.container, 'KICKASShidden');
      }
    },
    hideMenu: function () {
      this.container.style.bottom = '';
      addClass(this.container, 'KICKASShidden');
    },
    showBombMenu: function () {
      this.bombLink.style.width = "";
    },
    hideBombMenu: function () {
      this.bombLink.style.width = "0px";
    },
    getHeight: function () {
      return this.container.clientHeight;
    },
    isVisible: function () {
      return !hasClass(this.container, 'KICKASShidden');
    },
    addPoints: function (killed, pos) {
      var points = killed * this.game.multiplier;
      this.numPoints += points;
      this.points.innerHTML = this.numPoints;
      if (this.game.statisticsManager) {
        this.game.statisticsManager.increasePointsGainedWithPoints(points);
      }
      this.game.ui.addPointsBubbleAt(pos, points);
    },
    includeCSS: function (file) {
      var link = document.createElement('link');
      link.rel = 'stylesheet';
      link.type = 'text/css';
      link.href = file;
      (document.head || document.body).appendChild(link);
    },
    sendMessageToMenu: function (fragment) {
      if (this.menu) {
        this.menu.sendMessage(fragment);
      }
    },
    addWeapon: function (weapon, id) {
      var li = document.createElement('li');
      li.className = 'KICKASSELEMENT kickass-weapon-item';
      li.weapon = weapon;
      li.style.backgroundImage = 'url(' + GameGlobals.path('css/gfx/kickass/weap-' + weapon.id + '.png') + ')';
      li.innerHTML = '<span class="KICKASSELEMENT">' + weapon.name + '</span>';
      this.weaponsList.appendChild(li);
      addEvent(li, 'click', bind(this, function (e) {
        stopEvent(e);
        this.changeWeapon(weapon);
        this.sendMessageToMenu("changeWeapon:!" + id);
      }));
    },
    changeWeapon: function (weapon) {
      this.game.changeWeapon(weapon);
    },
    destroy: function () {
      var all = this.container.getElementsByTagName('*');
      for (var i = 0; i < all.length; i++) {
        this.game.unregisterElement(all[i]);
      }
      this.game.unregisterElement(this.container);
      if (this.menu) {
        this.menu.destroy();
      }
      this.container.parentNode.removeChild(this.container);
    }
  });
  var UIManager = new Class({
    initialize: function (game) {
      this.UNIQID = 0;
      this.game = game;
      this.pointBubbles = {};
      this.messages = {};
      this.fx = new Fx();
      this.fx.addListener(this);
    },
    update: function (tdelta) {
      this.fx.update();
    },
    set: function (key, value, delta) {
      var type = key.split('-')[0];
      var id = key.split('-')[1];
      if (this.pointBubbles[id]) {
        var bubble = this.pointBubbles[id];
        bubble.style.top = value[0] + 'px';
        bubble.style.opacity = value[1];
        if (delta == 1 && bubble.parentNode) {
          bubble.parentNode.removeChild(bubble);
          delete this.pointBubbles[id];
        }
      } else if (this.messages[id] && type == 'messagedown') {
        var message = this.messages[id];
        message.style.top = value[0] + 'px';
        if (delta == 1) {
          setTimeout(bind(this, function () {
            this.fx.add('messageup-' + id, {
              tweens: [
                [value[0], -100]
              ],
              transition: Tween.Quadratic,
              duration: 300
            });
          }), message.staytime || 4000);
        }
      } else if (this.messages[id] && type == 'messageup') {
        var message = this.messages[id];
        message.style.top = value[0] + 'px';
        if (delta == 1) {
          message.parentNode.removeChild(message);
          delete this.messages[id];
        }
      }
    },
    addPointsBubbleAt: function (pos, points) {
      var id = 'bubble' + (this.UNIQID++);
      var y = this.game.scrollPos.y + pos.y;
      var bubble = newElement('span', {
        innerHTML: points,
        className: 'KICKASSELEMENT',
        styles: {
          position: 'absolute',
          font: "20px Arial",
          fontWeight: "bold",
          opacity: "1",
          color: "black",
          textShadow: "#fff 1px 1px 3px",
          top: y,
          zIndex: "10000000"
        }
      });
      bubble.style.left = pos.x - bubble.offsetWidth / 2 + 'px';
      getAppContainerElement().appendChild(bubble);
      this.pointBubbles[id] = bubble;
      this.fx.add('bubble-' + id, {
        tweens: [
          [y, y - 15],
          [1, 0]
        ]
      });
    },
    showMessage: function (html, staytime) {
      staytime = staytime || false;
      var width = 300;
      var id = this.UNIQID++;
      var message = newElement('div', {
        innerHTML: html,
        className: 'KICKASSELEMENT',
        id: 'kickass-message-' + id,
        styles: {
          position: 'fixed',
          top: -100,
          left: '50%',
          marginLeft: -width / 2,
          width: width,
          background: '#222',
          opacity: 0.8,
          padding: '10px',
          color: '#fff',
          textAlign: 'center',
          borderRadius: 15,
          font: '20px Arial',
          fontWeight: 'bold',
          zIndex: "10000000"
        }
      });
      message.staytime = staytime;
      getAppContainerElement().appendChild(message);
      var to = this.getLowestBubbleY();
      message.kickassto = to;
      this.fx.add('messagedown-' + id, {
        duration: 300,
        tweens: [
          [-100, to]
        ],
        transition: Tween.Quadratic
      });
      this.messages[id] = message;
      return message;
    },
    getLowestBubbleY: function () {
      var top = 100;
      for (var id in this.messages)
        if (this.messages.hasOwnProperty(id))
          top = Math.max(this.messages[id].kickassto + this.messages[id].offsetHeight + 10, top);
      return top;
    }
  });
  var AudioManager = new Class({
    initialize: function (src, formats) {
      this.src = src;
      this.formats = formats;
      channels = 8;
      this.supportsAudio = (typeof document.createElement('audio').play) != 'undefined';
      if (this.supportsAudio) {
        this.numChannels = channels;
        this.channels = [];
        for (var i = 0; i < this.numChannels; i++) {
          this.channels.push({
            isPlaying: false,
            element: this.prepareElement(this.buildAudioElement())
          });
        }
      }
    },
    buildAudioElement: function () {
      var TYPES = {
        "ogg": "audio/ogg",
        "mp3": "audio/mpeg"
      }
      var audio = document.createElement("audio");
      for (var i = 0, format; format = this.formats[i]; i++) {
        var source = document.createElement("source");
        source.src = this.src + "." + format;
        source.type = TYPES[format];
        audio.appendChild(source);
      }
      return audio;
    },
    prepareElement: function (el) {
      if (typeof el.addEventListener == 'undefined')
        return el;
      var self = this;
      el.addEventListener('ended', function (e) {
        self.audioEnded(el);
      }, false);
      return el;
    },
    audioEnded: function (target) {
      for (var i = 0, channel; channel = this.channels[i]; i++) {
        if (channel.element === target) {
          channel.isPlaying = false;
        }
      }
    },
    play: function () {
      if (!this.supportsAudio)
        return;
      for (var i = 0, channel; channel = this.channels[i]; i++) {
        if (!channel.isPlaying) {
          channel.isPlaying = true;
          if (typeof channel.element.play == 'function')
            channel.element.play();
          return;
        }
      }
    }
  });
  var Ships = {
    Standard: {
      points: [
        [-10, 10],
        [0, -15],
        [10, 10]
      ],
      thrusters: [{
        s: {
          w: 20,
          h: 7
        },
        p: {
          x: 0,
          y: 14
        },
        a: 0
      }],
      cannons: [{
        p: {
          x: 0,
          y: -15
        },
        a: 0
      }]
    }
  };
  var PLAYERIDS = 0;
  var Player = new Class({
    initialize: function (game) {
      this.id = PLAYERIDS++;
      this.game = game;
      this.tween = false;
      this.isBound = true;
      this.pos = new Vector(200, 200);
      this.vel = new Vector(0, 0);
      this.acc = new Vector(0, 0);
      this.dir = new Vector(0, 1);
      this.currentRotation = 0;
      this.isBroken = false;
      this.lineOffsets = [];
      this.deadTime = 0;
      this.friction = 0.8;
      this.terminalVelocity = 2000;
      this.lastPos = new Vector(0, 0);
    },
    setShip: function (ship) {
      this.ship = ship;
      this.verts = [];
      for (var i = 0, vert; vert = this.ship.points[i]; i++)
        this.verts.push(new Vector(vert[0], vert[1]));
      this.verts.push(this.verts[0]);
      this.thrusters = [];
      this.cannons = [];
      this.addThrusters(this.ship.thrusters);
      this.addCannons(this.ship.cannons);
      this.size = this.getSizeFromVertsAndObjects();
      this.bounds = this.calculateBounds();
      if (this.sheet) {
        this.sheet.destroy();
      }
      this.sheet = new Sheet(new Rect(100, 100, this.bounds.x, this.bounds.y));
      this.forceRedraw = true;
    },
    setCannons: function (cannonClass) {
      var newCannons = [];
      for (var i = 0, cannon; cannon = this.cannons[i]; i++) {
        var newCannon = new cannonClass(this, this.game, cannon.pos.x, cannon.pos.y, cannon.angle);
        newCannons.push(newCannon);
        cannon.destroy();
      }
      this.cannons = newCannons;
    },
    addThrusters: function (thrusters) {
      for (var i = 0, data; data = thrusters[i]; i++) {
        var thruster = new Thruster(data);
        this.thrusters.push(thruster);
      }
    },
    addCannons: function (cannons) {
      for (var i = 0, data; data = cannons[i]; i++) {
        var weaponClass = WeaponMap[data.m] || WeaponMap.cannon;
        var cannon = new weaponClass.cannonClass(this, this.game, data.p.x, data.p.y, data.a);
        cannon.player = this;
        cannon.game = this.game;
        this.cannons.push(cannon);
      }
    },
    update: function (tdelta) {
      if (this.isBroken) {
        if (!this.lineOffsets.length) {
          for (var i = 0; i < (this.verts.length - 1); i++)
            this.lineOffsets[i] = {
              pos: new Vector(0, 0),
              dir: (new Vector(1, 1)).setAngle(Math.PI * 2 * Math.random())
            };
        }
        for (var i = 0; i < this.lineOffsets.length; i++) {
          this.lineOffsets[i].pos.add(this.lineOffsets[i].dir.cp().setLength(50).mul(tdelta));
        }
        this.sheet.clear();
        this.sheet.setAngle(this.dir.angle());
        this.sheet.setPosition(this.pos);
        this.sheet.drawBrokenPlayer(this.verts, this.lineOffsets);
        if (now() - this.deadTime > 1000.0) {
          this.isBroken = false;
          this.lineOffsets = [];
          this.randomPos();
        }
        return;
      }
      if (!this.tween) {
        if (this.game.isKeyPressed('left') || this.game.isKeyPressed('right')) {
          if (this.game.isKeyPressed('left'))
            this.rotateLeft(tdelta);
          if (this.game.isKeyPressed('right'))
            this.rotateRight(tdelta);
        } else {
          this.stopRotate();
        }
        if (this.game.isKeyPressed('up'))
          this.activateThrusters();
        else
          this.stopThrusters();
      }
      if (this.game.isKeyPressed(' ')) {
        this.isShooting = true;
        if (!this.isBroken)
          this.shootPressed();
      } else if (this.isShooting) {
        this.isShooting = false;
        this.shootReleased();
      }
      if (this.currentRotation)
        this.dir.setAngle(this.dir.angle() + this.currentRotation * tdelta);
      var frictionedAcc = this.acc.mulNew(tdelta).sub(this.vel.mulNew(tdelta * this.friction));
      this.vel.add(frictionedAcc);
      if (this.vel.len() > this.terminalVelocity)
        this.vel.setLength(this.terminalVelocity);
      var posDelta = this.vel.mulNew(tdelta);
      this.pos.add(posDelta);
      if (this.game.statisticsManager) {
        this.game.statisticsManager.increaseDistanceWithPixels(posDelta.len());
      }
      var showFlames = !this.acc.is({
        x: 0,
        y: 0
      });
      for (var i = 0, thruster; thruster = this.thrusters[i]; i++) {
        thruster.setIsShown(showFlames);
        thruster.update(tdelta);
      }
      if (this.isBound)
        this.checkBounds();
      if (!this.lastPos.is(this.pos) || this.currentRotation || this.forceRedraw) {
        this.forceRedraw = false;
        this.sheet.clear();
        this.sheet.setAngle(this.dir.angle() + Math.PI / 2);
        this.sheet.setPosition(this.pos);
        if (showFlames) {
          for (var i = 0, thruster; thruster = this.thrusters[i]; i++)
            thruster.drawTo(this.sheet);
        }
        this.sheet.drawPlayer(this.verts);
        this.lastPos = this.pos.cp();
      }
      for (var i = 0, cannon; cannon = this.cannons[i]; i++) {
        cannon.update(tdelta);
      }
    },
    randomPos: function () {
      var w = this.game.windowSize.width;
      var h = this.game.windowSize.height;
      this.pos = new Vector(random(0, w), random(0, h));
    },
    checkBounds: function () {
      if (this.tween)
        return;
      var w = this.game.windowSize.width;
      var h = this.game.windowSize.height;
      var rightBound = this.pos.x + this.sheet.rect.size.width / 2;
      var bottomBound = this.pos.y + this.sheet.rect.size.height / 2;
      if (rightBound > w) {
        window.scrollTo(this.game.scrollPos.x + 50, this.game.scrollPos.y);
        this.pos.x = 0;
      } else if (this.pos.x < 0) {
        window.scrollTo(this.game.scrollPos.x - 50, this.game.scrollPos.y);
        this.pos.x = w - this.sheet.rect.size.width / 2;
      }
      if (bottomBound > h) {
        window.scrollTo(this.game.scrollPos.x, this.game.scrollPos.y + h * 0.75);
        this.pos.y = 0;
      } else if (this.pos.y < 0) {
        window.scrollTo(this.game.scrollPos.x, this.game.scrollPos.y - h * 0.75);
        this.pos.y = h - this.sheet.rect.size.height / 2;
      }
    },
    inRect: function (rect) {
      var ret = false;
      for (var i = 0, vert; vert = this.verts[i]; i++) {
        if (rect.hasPoint(new Vector(vert.x + this.pos.x, vert.y + this.pos.y)))
          ret = true;
      }
      return ret;
    },
    hit: function (by) {
      if (this.isBroken)
        return;
      this.isBroken = true;
      this.deadTime = now();
    },
    activateThrusters: function () {
      if (this.game.statisticsManager) {
        this.game.statisticsManager.usedThrusters();
      }
      this.acc = (new Vector(500, 0)).setAngle(this.dir.angle());
    },
    stopThrusters: function () {
      this.acc = new Vector(0, 0);
    },
    rotateLeft: function (tdelta) {
      this.currentRotation = Math.max(-Math.PI * 2, this.currentRotation - Math.PI * 10 * tdelta);
    },
    rotateRight: function (tdelta) {
      this.currentRotation = Math.min(Math.PI * 2, this.currentRotation + Math.PI * 10 * tdelta);
    },
    stopRotate: function () {
      this.currentRotation = 0;
    },
    getSizeFromVertsAndObjects: function () {
      var largestDistance = 0;
      for (var i = 0, vert; vert = this.verts[i]; i++)
        largestDistance = Math.max(largestDistance, (new Vector(vert)).len());
      for (var i = 0, obj; obj = this.thrusters[i]; i++) {
        var p1 = (new Vector(obj.pos.x - obj.size.width / 2, obj.pos.y - obj.size.height / 2)).rotate(obj.angle);
        var p2 = (new Vector(obj.pos.x + obj.size.width / 2, obj.pos.y - obj.size.height / 2)).rotate(obj.angle);
        var p3 = (new Vector(obj.pos.x - obj.size.width / 2, obj.pos.y + obj.size.height / 2)).rotate(obj.angle);
        var p4 = (new Vector(obj.pos.x + obj.size.width / 2, obj.pos.y + obj.size.height / 2)).rotate(obj.angle);
        largestDistance = Math.max(largestDistance, p1.len(), p2.len(), p3.len(), p4.len());
      }
      return {
        width: largestDistance * 2,
        height: largestDistance * 2
      };
    },
    calculateBounds: function () {
      return {
        x: Math.max(this.size.width, this.size.height) * 1,
        y: Math.max(this.size.height, this.size.width) * 1
      };
    },
    shootPressed: function () {
      for (var i = 0, cannon; cannon = this.cannons[i]; i++)
        cannon.shootPressed();
    },
    shootReleased: function () {
      for (var i = 0, cannon; cannon = this.cannons[i]; i++)
        cannon.shootReleased();
    },
    flyTo: function (x, y, callback) {
      this.tween = {
        start: {
          pos: this.pos.cp(),
          dir: this.dir.cp()
        },
        to: new Vector(x, y),
        callback: callback || function () { }
      };
      this.tween.time = this.getTimeforTween();
    },
    destroy: function () {
      this.sheet.destroy();
    }
  });
  var Thruster = new Class({
    initialize: function (data, ship) {
      this.pos = new Vector(data.p);
      this.size = {
        width: data.s.w,
        height: data.s.h
      };
      this.angle = data.a || 0;
      this.ship = ship;
      this.isShown = false;
      this.flameY = 1;
      this.fx = new Fx();
      this.fx.addListener(this);
      this.flames = {
        r: [],
        y: []
      };
      this.lastFrameUpdate = 0;
      this.generateFlames();
    },
    update: function (tdelta) {
      this.fx.update();
      if (now() - this.lastFrameUpdate > 1000 / 60)
        this.generateFlames();
    },
    set: function (key, value) {
      switch (key) {
        case 'flames':
          this.flameY = value;
          break;
      }
    },
    setIsShown: function (isShown) {
      if (!this.isShown && isShown) {
        this.flameY = 0.0;
        this.generateFlames();
        this.fx.add('flames', {
          start: this.flameY,
          end: 1,
          duration: 250,
          transition: Tween.Quintic
        });
      }
      this.isShown = isShown;
    },
    drawTo: function (sheet) {
      sheet.drawFlames(this.flames, this.angle);
    },
    generateFlames: function () {
      var redWidth = this.size.width,
        redIncrease = this.size.width * 0.05,
        yellowWidth = this.size.width * 0.8,
        yellowIncrease = yellowWidth * 0.1,
        halfRed = redWidth / 2,
        halfYellow = yellowWidth / 2,
        offsetY = -this.size.height / 2,
        metaY = 0;
      var px = this.pos.x;
      var py = this.pos.y - this.size.height / 2;

      function vec(x, y) {
        return new Vector(x, y);
      }
      this.flames.r = [vec(-halfRed + px, py)];
      this.flames.y = [vec(-halfYellow + px, py)];
      this.flames.self = this;
      for (var x = 0; x < redWidth; x += redIncrease)
        this.flames.r.push(vec(x - halfRed + px, this.flameY * random(this.size.height * 0.7, this.size.height) + py));
      this.flames.r.push(vec(halfRed + px, py));
      for (var x = 0; x < yellowWidth; x += yellowIncrease)
        this.flames.y.push(vec(x - halfYellow + px, this.flameY * random(this.size.height * 0.4, this.size.height * 0.7) + py));
      this.flames.y.push(vec(halfYellow + px, py));
      this.lastFrameUpdate = now();
      var pos = vec(px, py);
      for (var i = 0, p; p = this.flames.r[i]; i++)
        p.sub(pos).rotate(this.angle).add(pos);
      for (var i = 0, p; p = this.flames.y[i]; i++)
        p.sub(pos).rotate(this.angle).add(pos);
    }
  });
  var BombManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.bombShowDelay = 30;
      this.nextBomb = this.bombShowDelay;
    },
    update: function (tdelta) {
      if (this.game.isKeyPressed('F') && this.isReady()) {
        this.blow();
      }
      if (this.nextBomb === -1 || !this.game.sessionManager.isPlaying) {
        return;
      }
      this.nextBomb -= tdelta;
      if (this.nextBomb < 0) {
        this.game.menuManager.showBombMenu();
        this.nextBomb = -1;
        this.game.ui.showMessage("BOMB IS READY<br />(lower right corner or F)");
      }
    },
    blow: function () {
      var message = this.game.ui.showMessage("3...", 5000);
      delay(1000, function () {
        message.innerHTML = "2...";
      }, this);
      delay(2000, function () {
        message.innerHTML = "1...";
      }, this);
      delay(3000, function () {
        message.innerHTML = "boom";
      }, this);
      delay(3000, this.blowStuffUp, this);
      this.nextBomb = this.bombShowDelay;
    },
    blowStuffUp: function () {
      this.game.bulletManager.updateEnemyIndex();
      var index = this.game.bulletManager.enemyIndex;
      for (var i = 0, el;
        (el = index[i]) && i < 10; i++) {
        var rect = getRect(el);
        var center = new Vector(rect.left + rect.width / 2, rect.top + rect.height / 2);
        this.game.explosionManager.addExplosion(center, el, MegaParticleExplosion);
        el.parentNode.removeChild(el);
      }
      this.game.menuManager.hideBombMenu();
      this.nextBomb = this.bombShowDelay;
    },
    isReady: function () {
      return this.nextBomb === -1;
    }
  });
  var ELEMENTSTHATARENOTTOBEINCLUDED = ['BR', 'SCRIPT', 'STYLE', 'TITLE', 'META', 'HEAD', 'OPTION', 'OPTGROUP', 'LINK'];
  var ELEMENTSIZETHRESHOLD = 5;
  var BulletManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.lastBlink = 0;
      this.blinkActive = false;
      this.enemyIndex = [];
      this.updateDelay = 2.5;
      this.nextUpdate = this.updateDelay;
    },
    update: function (tdelta) {
      if (this.game.isKeyPressed('B')) {
        this.blink();
      } else if (this.blinkActive) {
        this.endBlink();
      }
      this.nextUpdate -= tdelta;
      if (this.nextUpdate < 0) {
        this.updateEnemyIndex();
      }
    },
    blink: function () {
      if (now() - this.lastBlink > 250) {
        for (var i = 0, el; el = this.enemyIndex[i]; i++) {
          if (!this.blinkActive)
            el.style.outline = '1px solid red';
          else
            el.style.outline = el.KICKASSOLDBORDER;
        }
        this.blinkActive = !this.blinkActive;
        this.lastBlink = now();
        if (!this.blinkActive) {
          this.updateEnemyIndex();
        }
      }
    },
    endBlink: function () {
      for (var i = 0, el; el = this.enemyIndex[i]; i++)
        el.style.outline = el.KICKASSOLDBORDER;
      this.lastBlink = 0;
      this.blinkActive = false;
    },
    updateEnemyIndex: function () {
      var all = document.getElementsByTagName('*');
      this.enemyIndex = [];
      for (var i = 0, el; el = all[i]; i++) {
        if (this.isDestroyable(el)) {
          this.enemyIndex.push(el);
          el.KICKASSOLDBORDER = el.style.outline || (document.defaultView.getComputedStyle(el, null).outline);
        }
      }
      this.nextUpdate = this.updateDelay;
    },
    isDestroyable: function (element, ignoreSize) {
      if (this.shouldIgnoreElement(element, ignoreSize))
        return false;
      for (var i = 0, child; child = element.childNodes[i]; i++) {
        if (child.nodeType === 1 && ELEMENTSTHATARENOTTOBEINCLUDED.indexOf(child.tagName) === -1 && (child.offsetWidth >= ELEMENTSIZETHRESHOLD && child.offsetHeight >= ELEMENTSIZETHRESHOLD) && document.defaultView.getComputedStyle(child, null).visibility !== 'hidden') {
          return false;
        }
      }
      return true;
    },
    isDestroyableFromCollision: function (element) {
      return this.isDestroyable(element, true);
    },
    shouldIgnoreElement: function (element, ignoreSize) {
      if (element.nodeType !== 1)
        return true;
      if (element == document.documentElement || element == document.body)
        return true;
      if (ELEMENTSTHATARENOTTOBEINCLUDED.indexOf(element.tagName) !== -1)
        return true;
      if (element.style.visibility == 'hidden' || element.style.display == 'none')
        return true;
      if (typeof element.className == "string" && element.className.indexOf('KICKASSELEMENT') != -1)
        return true;
      if (!ignoreSize) {
        if (element.offsetWidth < ELEMENTSIZETHRESHOLD || element.offsetHeight < ELEMENTSIZETHRESHOLD)
          return true;
      }
      var rect;
      if (element.offsetLeft < 0 || element.offsetTop < 0) {
        rect = getRect(element);
        if (rect.left + rect.width < 0 || rect.top + rect.height < 0)
          return true;
      }
      if (!rect)
        rect = getRect(element);
      if (rect.top >= this.game.scrollSize.y)
        return true;
      return false;
    },
    destroy: function () {
      for (var key in this.bullets)
        if (this.bullets.hasOwnProperty(key))
          for (var i = 0, bullet; bullet = this.bullets[key][i]; i++)
            bullet.destroy();
      this.bullets = {};
    }
  });
  var SessionManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.isPlaying = false;
    },
    update: function (tdelta) {
      if (this.isPlaying && this.game.bulletManager.enemyIndex.length == 0) {
        this.weHaveWon();
      }
    },
    weHaveWon: function () {
      this.isPlaying = false;
      this.game.ui.showMessage("You're done!");
      if (this.game.isCampaign()) {
        this.game.menuManager.showMenu();
        this.game.menuManager.navigateTo('highscores');
      } else {
        this.game.menuManager.showMenu();
      }
      this.game.menuManager.sendMessageToMenu("gameFinished:!");
    }
  });
  var ExplosionManager = new Class({
    initialize: function (game) {
      this.game = game;
      this.explosions = [];
    },
    update: function (tdelta) {
      var time = now();
      for (var i = 0, explosion; explosion = this.explosions[i]; i++) {
        if (time - explosion.bornAt > (explosion.ttl || 500)) {
          explosion.destroy();
          this.explosions.splice(i, 1);
          continue;
        }
        explosion.update(tdelta);
      }
    },
    addExplosion: function (pos, forElement, explosionClass) {
      explosionClass = explosionClass || ParticleExplosion;
      var explosion = new explosionClass(pos, forElement);
      explosion.game = this.game;
      explosion.checkBounds();
      this.explosions.push(explosion);
      if (this.game.audioManager.explosion) {
        this.game.audioManager.explosion.play();
      }
    },
    destroy: function () {
      for (var i = 0, explosion; explosion = this.explosions[i]; i++)
        explosion.destroy();
      this.explosions = [];
    }
  });
  var Cannon = new Class({
    initialize: function (player, game, x, y, angle) {
      this.player = player;
      this.game = game;
      this.pos = new Vector(x, y);
      this.angle = angle || 0;
    },
    shootPressed: function () { },
    shootReleased: function () { },
    checkCollisions: function () { },
    getExplosionClass: function () {
      return ParticleExplosion;
    },
    update: function (tdelta) {
      this.game.hideAll();
      this.checkCollisions(tdelta);
      this.game.showAll();
    },
    checkCollision: function (bullet) {
      var hit = bullet.checkCollision();
      if (!hit)
        return false;
      this.game.explosionManager.addExplosion(bullet.pos, hit, this.getExplosionClass());
      this.game.menuManager.addPoints(Math.min(hit.getElementsByTagName('*').length + 1, 100), bullet.pos);
      if (!hit.isShot) {
        hit.parentNode.removeChild(hit);
      }
      if (this.game.statisticsManager) {
        this.game.statisticsManager.addElementsDestroyed();
      }
      return true;
    },
    createBullet: function (bulletClass) {
      var pos = this.getABulletPos();
      var dir = this.getABulletDir();
      var bullet = new bulletClass(pos, dir);
      bullet.game = this.game;
      bullet.manager = this;
      bullet.initCanvas();
      bullet.vel.add(bullet.vel.cp().setLength(this.player.vel.len()));
      return bullet;
    },
    getABulletPos: function () {
      return this.player.pos.cp().add(this.pos.cp().rotate(this.player.dir.angle() + Math.PI / 2));
    },
    getABulletDir: function () {
      return this.player.dir.cp().rotate(this.angle);
    },
    destroy: function () { }
  });
  var LaserCannon = new Class({
    Extends: Cannon,
    initialize: function (player, game, x, y, angle) {
      Cannon.prototype.initialize.apply(this, arguments);
      this.lasers = [];
    },
    getExplosionClass: function () {
      return SplitExplosion;
    },
    update: function (tdelta) {
      if (!this.lasers.length)
        return;
      this.removeOld();
      Cannon.prototype.update.call(this, tdelta);
    },
    checkCollisions: function (tdelta) {
      for (var i = 0, laser; laser = this.lasers[i]; i++) {
        laser.update(tdelta);
        if (this.checkCollision(laser)) { }
      }
    },
    removeOld: function () {
      for (var i = 0, laser; laser = this.lasers[i]; i++) {
        if (laser.outOfBounds) {
          laser.destroy();
          this.lasers.splice(i, 1);
        }
      }
    },
    shootPressed: function () {
      if (this.lasers.length > 5) {
        return;
      }
      if (now() - this.lastFired < 500) {
        return;
      }
      this.lastFired = now();
      if (this.game.statisticsManager) {
        this.game.statisticsManager.addShotFired();
      }
      this.lasers.push(this.createBullet(LaserBullet));
    },
    destroy: function () {
      if (this.lasers.length) {
        for (var i = 0, laser; laser = this.lasers[i]; i++) {
          laser.destroy();
        }
        this.lasers = [];
      }
    }
  });
  var BallCannon = new Class({
    Extends: Cannon,
    initialize: function () {
      Cannon.prototype.initialize.apply(this, arguments);
      this.lastFired = 0;
      this.bullets = [];
    },
    getExplosionClass: function () {
      return ParticleExplosion;
    },
    update: function (tdelta) {
      if (!this.bullets.length) {
        return;
      }
      this.removeOld();
      Cannon.prototype.update.call(this, tdelta);
    },
    removeOld: function () {
      var time = now();
      for (var i = 0, bullet; bullet = this.bullets[i]; i++) {
        if (time - bullet.bornAt > 2000) {
          bullet.destroy();
          this.bullets.splice(i, 1);
        }
      }
    },
    checkCollisions: function (tdelta) {
      for (var i = 0, bullet; bullet = this.bullets[i]; i++) {
        bullet.update(tdelta);
        if (this.checkCollision(bullet)) {
          bullet.destroy();
          this.bullets.splice(i, 1);
        }
      }
    },
    shootPressed: function () {
      if (now() - this.lastFired < 200) {
        return;
      }
      this.lastFired = now();
      this.addBullet();
      if (this.game.statisticsManager) {
        this.game.statisticsManager.addShotFired();
      }
    },
    addBullet: function () {
      if (this.bullets.length > 7) {
        this.bullets[0].destroy();
        this.bullets.shift();
      }
      var bullet = this.createBullet(Bullet);
      this.bullets.push(bullet);
    },
    destroy: function () {
      for (var i = 0, bullet; bullet = this.bullets[i]; i++) {
        bullet.destroy();
      }
      this.bullets = [];
    }
  });
  var Bullet = new Class({
    initialize: function (pos, dir) {
      this.pos = pos.cp();
      this.dir = dir;
      this.vel = new Vector(500, 500);
      this.bornAt = now();
    },
    initCanvas: function () {
      this.sheet = new Sheet(new Rect(this.pos.x, this.pos.y, 5, 5));
      this.sheet.drawBullet();
    },
    draw: function () {
      this.sheet.setPosition(this.pos);
    },
    update: function (tdelta) {
      this.pos.add(this.vel.setAngle(this.dir.angle()).mulNew(tdelta));
      this.checkBounds();
      this.draw();
    },
    checkCollision: function () {
      var element = document.elementFromPoint(this.pos.x, this.pos.y);
      if (element && element.nodeType == 3)
        element = element.parentNode;
      var didFind = element && this.game.bulletManager.isDestroyableFromCollision(element) ? element : false;
      return didFind;
    },
    checkBounds: function () {
      var w = this.game.windowSize.width;
      var h = this.game.windowSize.height;
      var rightBound = this.pos.x + this.sheet.rect.size.width / 2;
      var bottomBound = this.pos.y + this.sheet.rect.size.height / 2;
      if (rightBound > w)
        this.pos.x = 0;
      else if (this.pos.x < 0)
        this.pos.x = w - this.sheet.rect.size.width / 2;
      if (bottomBound > h)
        this.pos.y = 0;
      else if (this.pos.y < 0)
        this.pos.y = h - this.sheet.rect.size.height / 2;
    },
    destroy: function () {
      this.sheet.destroy();
    }
  });
  var LaserBullet = new Class({
    Extends: Bullet,
    initialize: function () {
      Bullet.prototype.initialize.apply(this, arguments);
      this.vel = new Vector(750, 750);
      this.lastDrawPos = this.pos.cp();
    },
    initCanvas: function () {
      var s = Math.max(GameGlobals.laserImage.width, GameGlobals.laserImage.height);
      this.sheet = new Sheet(new Rect(0, 0, s, s));
    },
    update: function (tdelta) {
      Bullet.prototype.update.apply(this, arguments);
    },
    draw: function () {
      this.sheet.drawLaser(this.pos, this.dir);
      this.lastDrawPos = this.pos.cp();
    },
    checkBounds: function () {
      var w = this.game.windowSize.width;
      var h = this.game.windowSize.height;
      var rightBound = this.pos.x + this.sheet.rect.size.width / 2;
      var bottomBound = this.pos.y + this.sheet.rect.size.height / 2;
      if (rightBound > w || this.pos.x < 0)
        this.outOfBounds = true;
      if (bottomBound > h || this.pos.y < 0)
        this.outOfBounds = true;
    },
    destroy: function () {
      this.sheet.destroy();
    }
  });
  GameGlobals.laserImage = document.createElement('img');
  GameGlobals.laserImage.src = GameGlobals.path('css/gfx/kickass/laser.png');
  var Explosion = new Class({
    initialize: function (pos, element) {
      this.bornAt = now();
      this.pos = pos.cp();
    },
    update: function (tdelta) { },
    checkBounds: function () { },
    destroy: function () { }
  });
  var ParticleExplosion = new Class({
    Extends: Explosion,
    initialize: function (pos, element) {
      Explosion.prototype.initialize.apply(this, arguments);
      this.particleVel = new Vector(150, 0);
      this.particles = [];
      this.generateParticles();
      this.sheet = new Sheet(new Rect(pos.x, pos.y, 250, 250));
    },
    update: function (tdelta) {
      for (var i = 0, particle; particle = this.particles[i]; i++)
        particle.pos.add(particle.vel.mulNew(tdelta).mul(random(0.5, 1.0)).setAngle(particle.dir.angle()));
      this.sheet.clear();
      this.sheet.drawExplosion(this.particles);
    },
    generateParticles: function () {
      for (var i = 0, j = !GameGlobals.hasCanvas ? 10 : 40; i < j; i++) {
        this.particles.push({
          dir: (new Vector(random(0, 20) - 10, random(0, 20) - 10)).normalize(),
          vel: this.particleVel.cp(),
          pos: new Vector(0, 0),
          color: ['yellow', 'red'][random(0, 1)]
        });
      }
    },
    checkBounds: function () {
      var right = this.sheet.rect.getRight();
      var bottom = this.sheet.rect.getBottom();
      var w = this.game.windowSize.width;
      var h = this.game.windowSize.height;
      if (right > w)
        this.pos.x -= right - w;
      if (bottom > h)
        this.pos.y -= bottom - h;
      this.sheet.setPosition(this.pos);
    },
    destroy: function () {
      this.sheet.destroy();
    }
  });
  var MegaParticleExplosion = new Class({
    Extends: ParticleExplosion,
    initialize: function (pos, element) {
      Explosion.prototype.initialize.apply(this, arguments);
      this.particleVel = new Vector(200, 0);
      this.particles = [];
      this.generateParticles();
      this.sheet = new Sheet(new Rect(pos.x, pos.y, 500, 500));
      this.ttl = 2000;
      this.generationDelay = 0.6;
      this.generationTimes = 2;
      this.nextGenerate = this.generationDelay;
    },
    update: function (tdelta) {
      this.nextGenerate -= tdelta;
      if (this.nextGenerate <= 0 && this.generationTimes > 0) {
        this.nextGenerate = this.generationDelay;
        this.generateParticles();
        this.generationTimes--;
      }
      ParticleExplosion.prototype.update.call(this, tdelta);
    }
  });
  var SplitExplosion = new Class({
    Extends: Explosion,
    initialize: function (pos, element) {
      if (!element)
        return;
      Explosion.prototype.initialize.apply(this, arguments);
      this.element = element;
      this.fx = new Fx();
      this.fx.addListener(this);
      this.start();
    },
    update: function (tdelta) {
      if (!this.element)
        return;
      this.fx.update();
    },
    set: function (key, value) {
      if (key == 'opacity') { }
    },
    start: function () {
      var pieces = this.createClones();
      var left = pieces[0],
        right = pieces[1];
      var lT = 'rotate(-' + random(30, 50) + 'deg) translate(-100px, 40px)';
      var rT = 'rotate(' + random(30, 50) + 'deg) translate(100px, 40px)';
      setStyles(left, {
        'transform': lT
      });
      setStyles(right, {
        'transform': rT
      });
      this.left = left;
      this.right = right;
      this.fx.add('opacity', {
        start: 1,
        end: 0.5,
        duration: 500
      });
    },
    createClones: function () {
      var coords = getRect(this.element);
      var leftContainer = this.createContainer(coords);
      var rightContainer = this.createContainer(coords);
      var left = cloneElement(this.element);
      var right = cloneElement(this.element);
      addClass(left, 'KICKASSELEMENT');
      addClass(right, 'KICKASSELEMENT');
      var styles = {
        margin: 0,
        overflow: 'hidden'
      };
      setStyles(left, styles);
      setStyles(right, styles);
      leftContainer.appendChild(left);
      rightContainer.appendChild(right);
      rightContainer.style.left = coords.left + coords.width / 2 + 'px';
      rightContainer.scrollLeft += coords.width / 2;
      this.element.style.opacity = 0;
      this.element.style.visibility = 'hidden';
      this.element.style.display = 'none';
      return each([leftContainer, rightContainer], function (el) {
        el.style.transition = 'transform 500ms ease-in';
      });
    },
    createContainer: function (coords) {
      var ret = document.createElement('div');
      setStyles(ret, {
        position: 'absolute',
        left: coords.left,
        top: coords.top,
        width: coords.width * 0.5,
        height: coords.height,
        overflow: 'hidden'
      });
      getAppContainerElement().appendChild(ret);
      return ret;
    },
    destroy: function () {
      try {
        this.left.parentNode.removeChild(this.left);
        this.right.parentNode.removeChild(this.right);
        this.element.parentNode.removeChild(this.element);
      } catch (e) { }
    }
  });
  var Weapons = {
    1: {
      name: 'Cannon',
      id: 'cannon',
      cannonClass: BallCannon
    },
    2: {
      name: 'Laser',
      id: 'laser',
      cannonClass: LaserCannon
    }
  };
  var WeaponMap = {
    'cannon': Weapons[1],
    'laser': Weapons[2]
  };
  var SheetCanvas = new Class({
    initialize: function (rect) {
      this.canvas = document.createElement('canvas');
      this.canvas.className = 'KICKASSELEMENT';
      with (this.canvas.style) {
        position = 'absolute';
        zIndex = '1000000';
      }
      GameGlobals.kickass.registerElement(this.canvas);
      if (this.canvas.getContext)
        this.ctx = this.canvas.getContext('2d');
      this.rect = rect;
      this.angle = 0;
      this.updateCanvas();
      getAppContainerElement().appendChild(this.canvas);
    },
    tracePoly: function (verts) {
      if (!verts[0])
        return;
      this.ctx.save();
      this.ctx.translate(this.rect.size.width / 2, this.rect.size.height / 2);
      this.ctx.rotate(this.angle);
      this.ctx.beginPath();
      this.ctx.moveTo(verts[0].x, verts[0].y);
      for (var i = 0; i < verts.length; i++) {
        this.ctx.lineTo(verts[i].x, verts[i].y);
      }
      this.ctx.restore();
    },
    setAngle: function (angle) {
      this.angle = angle;
    },
    updateCanvas: function () {
      if (this.canvas.width != this.rect.size.width)
        this.canvas.width = this.rect.size.width;
      if (this.canvas.height != this.rect.size.height)
        this.canvas.height = this.rect.size.height;
      this.canvas.style.left = GameGlobals.kickass.scrollPos.x + (this.rect.pos.x - this.rect.size.width / 2) + 'px';
      this.canvas.style.top = GameGlobals.kickass.scrollPos.y + (this.rect.pos.y - this.rect.size.height / 2) + 'px';
    },
    drawLine: function (xFrom, yFrom, xTo, yTo) {
      this.ctx.save();
      this.ctx.translate(this.rect.size.width / 2, this.rect.size.height / 2);
      this.ctx.beginPath();
      this.ctx.moveTo(xFrom, yFrom);
      this.ctx.lineTo(xTo, yTo);
      this.ctx.closePath();
      this.ctx.stroke();
      this.ctx.restore();
    },
    drawCircle: function (radius, pos) {
      pos = pos || {
        x: 0,
        y: 0
      };
      this.ctx.save();
      this.ctx.translate(this.rect.size.width / 2, this.rect.size.height / 2);
      if (pos)
        this.ctx.translate(pos.x, pos.y);
      this.ctx.beginPath();
      this.ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
      this.ctx.fill();
      this.ctx.closePath();
      this.ctx.restore();
    },
    drawRect: function (x, y, w, h) {
      this.ctx.save();
      this.ctx.translate(this.rect.size.width / 2, this.rect.size.height / 2);
      this.ctx.translate(x, y);
      this.ctx.fillRect(x, y, w, h);
      this.ctx.restore();
      this.ctx.fill();
    },
    drawImageFull: function (image) {
      this.ctx.drawImage(image, 0, 0, this.rect.size.width, this.rect.size.height);
    },
    drawImage: function (image, x, y) {
      this.ctx.save();
      this.ctx.translate(this.rect.size.width / 2 + x, this.rect.size.height / 2 + y);
      this.ctx.rotate(this.angle);
      this.ctx.drawImage(image, 0, -11);
      this.ctx.restore();
    },
    setFillColor: function (color) {
      this.ctx.fillStyle = color;
    },
    setStrokeColor: function (color) {
      this.ctx.strokeStyle = color;
    },
    setLineWidth: function (width) {
      this.ctx.lineWidth = width;
    },
    fillPath: function () {
      this.ctx.fill();
    },
    strokePath: function () {
      this.ctx.stroke();
    },
    clear: function () {
      this.ctx.clearRect(0, 0, this.rect.size.width, this.rect.size.height);
    },
    destroy: function () {
      GameGlobals.kickass.unregisterElement(this.canvas);
      this.canvas.parentNode.removeChild(this.canvas);
    }
  });
  var Sheet = new Class({
    initialize: function (rect) {
      this.rect = rect;
      this.drawer = new SheetCanvas(rect);
    },
    clear: function () {
      this.drawer.clear();
    },
    setPosition: function (pos) {
      this.rect.pos = pos.cp();
      this.drawer.rect = this.rect;
      this.drawer.updateCanvas();
    },
    setAngle: function (angle) {
      this.drawer.setAngle(angle);
    },
    drawPlayer: function (verts) {
      this.drawer.setFillColor('white');
      this.drawer.setStrokeColor('black');
      this.drawer.setLineWidth(1.5);
      this.drawer.tracePoly(verts);
      this.drawer.fillPath();
      this.drawer.tracePoly(verts);
      this.drawer.strokePath();
    },
    drawBrokenPlayer: function (verts, lineOffsets) {
      this.drawer.setStrokeColor('black');
      this.drawer.setLineWidth(1.5);
      for (var i = 1, vert, lastVert = verts[0]; vert = verts[i]; i++,
        lastVert = vert) {
        var o = lineOffsets[i - 1];
        this.drawer.drawLine(lastVert.x + o.pos.x, lastVert.x + o.pos.y, vert.x + o.pos.x, vert.y + o.pos.y);
      }
    },
    drawFlames: function (flames, angle) {
      this.drawer.setLineWidth(1.5);
      this.drawer.setFillColor('red');
      this.drawer.tracePoly(flames.r);
      this.drawer.fillPath();
      this.drawer.setFillColor('yellow');
      this.drawer.tracePoly(flames.y);
      this.drawer.fillPath();
    },
    drawBullet: function () {
      this.drawer.setFillColor(GameGlobals.bulletColor);
      this.drawer.drawCircle(2.5);
    },
    drawExplosion: function (particles) {
      for (var i = 0, particle; particle = particles[i]; i++) {
        this.drawer.setFillColor(particle.color);
        this.drawer.drawRect(particle.pos.x, particle.pos.y, 3, 3);
      }
    },
    drawFace: function (face) {
      this.drawer.drawImageFull(face);
    },
    drawLaser: function (pos, dir) {
      this.clear();
      this.setPosition(pos);
      this.drawer.setAngle(dir.angle());
      this.drawer.drawImage(GameGlobals.laserImage, 0, 0);
    },
    transformToSheetCoordinates: function (vec) {
      var ret = vec.cp().sub(new Vector(this.rect.size.width / 2, this.rect.size.height / 2));
      return ret;
    },
    destroy: function () {
      this.drawer.destroy();
    }
  });
  var namespace = getGlobalNamespace();

  var initKickAss = function () {
    // If an instance of KickAss is already present, we add a player
    if (!namespace.KICKASSGAME) {
      if (namespace.KICKASS_SITE_KEY) {
        var mySite = new MySite(namespace.KICKASS_SITE_KEY);
        mySite.load(function (ok) {
          namespace.KICKASSGAME = GameGlobals.kickass = new KickAss({
            mySite: ok ? mySite : false
          });

          namespace.KICKASSGAME.begin();
        });
      } else {
        namespace.KICKASSGAME = GameGlobals.kickass = new KickAss();
        namespace.KICKASSGAME.begin();
      }
    } else {
      namespace.KICKASSGAME.addPlayer();
    }
  };

  if (namespace.LAZYLOAD_KICKASS) {
    window.startBrowserBlaster = initKickAss;
  } else {
    initKickAss();
  }

  function getAppContainerElement() {
    if (namespace.KICKASS_CONTAINER_ELEMENT) {
      return document.getElementById(namespace.KICKASS_CONTAINER_ELEMENT);
    } else {
      return document.body;
    }
  }
}