Cow

gives you magical powers (fixed by Wealthy)

// ==UserScript==
// @name         Cow
// @description  gives you magical powers (fixed by Wealthy)
// @version      1
// @author       Yendis
// @match        *://*.moomoo.io/*
// @grant        none
// @icon         https://moomoo.io/img/animals/cow_1.png
// @license      MIT
// @namespace https://greasyfork.org/users/1360517
// ==/UserScript==

const gridSize = 40,
      stackTexts = true;

//addEventListener("DOMContentLoaded", window.FRVR.bootstrapper.complete);

const require = [{
    name: "msgpack",
    url: "https://rawgit.com/kawanet/msgpack-lite/master/dist/msgpack.min.js",
    data: "msgpack"
}];

const required = {};

(async () => {
    let index = 0;

    while(index < require.length) {
        const lib = require[index];

        try {
            await fetch(lib.url).then(response => response.text()).then(response => eval(response));

            required[lib.name] = window[lib.data];
        } catch(event){}

        index += 1;
    }

    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));


    /******/ (function(modules) { // webpackBootstrap
        /******/ 	// The module cache
        /******/ 	var installedModules = {};
        /******/
        /******/ 	// The require function
        /******/ 	function __webpack_require__(moduleId) {
            /******/
            /******/ 		// Check if module is in cache
            /******/ 		if(installedModules[moduleId]) {
                /******/ 			return installedModules[moduleId].exports;
                /******/ 		}
            /******/ 		// Create a new module (and put it into the cache)
            /******/ 		var module = installedModules[moduleId] = {
                /******/ 			i: moduleId,
                /******/ 			l: false,
                /******/ 			exports: {}
                /******/ 		};
            /******/
            /******/ 		// Execute the module function
            console.log(moduleId)
            /******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
            /******/
            /******/ 		// Flag the module as loaded
            /******/ 		module.l = true;
            /******/
            /******/ 		// Return the exports of the module
            /******/ 		return module.exports;
            /******/ 	}
        /******/
        /******/
        /******/ 	// expose the modules object (__webpack_modules__)
        /******/ 	__webpack_require__.m = modules;
        /******/
        /******/ 	// expose the module cache
        /******/ 	__webpack_require__.c = installedModules;
        /******/
        /******/ 	// define getter function for harmony exports
        /******/ 	__webpack_require__.d = function(exports, name, getter) {
            /******/ 		if(!__webpack_require__.o(exports, name)) {
                /******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
                /******/ 		}
            /******/ 	};
        /******/
        /******/ 	// define __esModule on exports
        /******/ 	__webpack_require__.r = function(exports) {
            /******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
                /******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
                /******/ 		}
            /******/ 		Object.defineProperty(exports, '__esModule', { value: true });
            /******/ 	};
        /******/
        /******/ 	// create a fake namespace object
        /******/ 	// mode & 1: value is a module id, require it
        /******/ 	// mode & 2: merge all properties of value into the ns
        /******/ 	// mode & 4: return value when already ns object
        /******/ 	// mode & 8|1: behave like require
        /******/ 	__webpack_require__.t = function(value, mode) {
            /******/ 		if(mode & 1) value = __webpack_require__(value);
            /******/ 		if(mode & 8) return value;
            /******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
            /******/ 		var ns = Object.create(null);
            /******/ 		__webpack_require__.r(ns);
            /******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
            /******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
            /******/ 		return ns;
            /******/ 	};
        /******/
        /******/ 	// getDefaultExport function for compatibility with non-harmony modules
        /******/ 	__webpack_require__.n = function(module) {
            /******/ 		var getter = module && module.__esModule ?
                /******/ 			function getDefault() { return module['default']; } :
            /******/ 			function getModuleExports() { return module; };
            /******/ 		__webpack_require__.d(getter, 'a', getter);
            /******/ 		return getter;
            /******/ 	};
        /******/
        /******/ 	// Object.prototype.hasOwnProperty.call
        /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
        /******/
        /******/ 	// __webpack_public_path__
        /******/ 	__webpack_require__.p = "";
        /******/
        /******/
        /******/ 	// Load entry module and return exports
        /******/ 	return __webpack_require__(__webpack_require__.s = "./src/js/app.js");
        /******/ })
    ({
        /******/ "./node_modules/buffer/node_modules/isarray/index.js": (function(module, exports) {

            var toString = {}.toString;

            module.exports = Array.isArray || function (arr) {
                return toString.call(arr) == '[object Array]';
            };


            /***/ }),

        /***/ "./node_modules/process/browser.js":
        /*!*****************************************!*\
  !*** ./node_modules/process/browser.js ***!
  \*****************************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            // shim for using process in browser
            var process = module.exports = {};

            // cached from whatever global is present so that test runners that stub it
            // don't break things.  But we need to wrap it in a try catch in case it is
            // wrapped in strict mode code which doesn't define any globals.  It's inside a
            // function because try/catches deoptimize in certain engines.

            var cachedSetTimeout;
            var cachedClearTimeout;

            function defaultSetTimout() {
                throw new Error('setTimeout has not been defined');
            }
            function defaultClearTimeout () {
                throw new Error('clearTimeout has not been defined');
            }
            (function () {
                try {
                    if (typeof setTimeout === 'function') {
                        cachedSetTimeout = setTimeout;
                    } else {
                        cachedSetTimeout = defaultSetTimout;
                    }
                } catch (e) {
                    cachedSetTimeout = defaultSetTimout;
                }
                try {
                    if (typeof clearTimeout === 'function') {
                        cachedClearTimeout = clearTimeout;
                    } else {
                        cachedClearTimeout = defaultClearTimeout;
                    }
                } catch (e) {
                    cachedClearTimeout = defaultClearTimeout;
                }
            } ())
            function runTimeout(fun) {
                if (cachedSetTimeout === setTimeout) {
                    //normal enviroments in sane situations
                    return setTimeout(fun, 0);
                }
                // if setTimeout wasn't available but was latter defined
                if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
                    cachedSetTimeout = setTimeout;
                    return setTimeout(fun, 0);
                }
                try {
                    // when when somebody has screwed with setTimeout but no I.E. maddness
                    return cachedSetTimeout(fun, 0);
                } catch(e){
                    try {
                        // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
                        return cachedSetTimeout.call(null, fun, 0);
                    } catch(e){
                        // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
                        return cachedSetTimeout.call(this, fun, 0);
                    }
                }


            }
            function runClearTimeout(marker) {
                if (cachedClearTimeout === clearTimeout) {
                    //normal enviroments in sane situations
                    return clearTimeout(marker);
                }
                // if clearTimeout wasn't available but was latter defined
                if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
                    cachedClearTimeout = clearTimeout;
                    return clearTimeout(marker);
                }
                try {
                    // when when somebody has screwed with setTimeout but no I.E. maddness
                    return cachedClearTimeout(marker);
                } catch (e){
                    try {
                        // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
                        return cachedClearTimeout.call(null, marker);
                    } catch (e){
                        // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
                        // Some versions of I.E. have different rules for clearTimeout vs setTimeout
                        return cachedClearTimeout.call(this, marker);
                    }
                }



            }
            var queue = [];
            var draining = false;
            var currentQueue;
            var queueIndex = -1;

            function cleanUpNextTick() {
                if (!draining || !currentQueue) {
                    return;
                }
                draining = false;
                if (currentQueue.length) {
                    queue = currentQueue.concat(queue);
                } else {
                    queueIndex = -1;
                }
                if (queue.length) {
                    drainQueue();
                }
            }

            function drainQueue() {
                if (draining) {
                    return;
                }
                var timeout = runTimeout(cleanUpNextTick);
                draining = true;

                var len = queue.length;
                while(len) {
                    currentQueue = queue;
                    queue = [];
                    while (++queueIndex < len) {
                        if (currentQueue) {
                            currentQueue[queueIndex].run();
                        }
                    }
                    queueIndex = -1;
                    len = queue.length;
                }
                currentQueue = null;
                draining = false;
                runClearTimeout(timeout);
            }

            process.nextTick = function (fun) {
                var args = new Array(arguments.length - 1);
                if (arguments.length > 1) {
                    for (var i = 1; i < arguments.length; i++) {
                        args[i - 1] = arguments[i];
                    }
                }
                queue.push(new Item(fun, args));
                if (queue.length === 1 && !draining) {
                    runTimeout(drainQueue);
                }
            };

            // v8 likes predictible objects
            function Item(fun, array) {
                this.fun = fun;
                this.array = array;
            }
            Item.prototype.run = function () {
                this.fun.apply(null, this.array);
            };
            process.title = 'browser';
            process.browser = true;
            process.env = {};
            process.argv = [];
            process.version = ''; // empty string to avoid regexp issues
            process.versions = {};

            function noop() {}

            process.on = noop;
            process.addListener = noop;
            process.once = noop;
            process.off = noop;
            process.removeListener = noop;
            process.removeAllListeners = noop;
            process.emit = noop;
            process.prependListener = noop;
            process.prependOnceListener = noop;

            process.listeners = function (name) { return [] }

            process.binding = function (name) {
                throw new Error('process.binding is not supported');
            };

            process.cwd = function () { return '/' };
            process.chdir = function (dir) {
                throw new Error('process.chdir is not supported');
            };
            process.umask = function() { return 0; };


            /***/ }),

        /***/ "./node_modules/punycode/punycode.js":
        /*!*******************************************!*\
  !*** ./node_modules/punycode/punycode.js ***!
  \*******************************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            /* WEBPACK VAR INJECTION */(function(module, global) {var __WEBPACK_AMD_DEFINE_RESULT__;/*! https://mths.be/punycode v1.4.1 by @mathias */
                                                                  ;(function(root) {

                                                                      /** Detect free variables */
                                                                      var freeExports =  true && exports &&
                                                                          !exports.nodeType && exports;
                                                                      var freeModule =  true && module &&
                                                                          !module.nodeType && module;
                                                                      var freeGlobal = typeof global == 'object' && global;
                                                                      if (
                                                                          freeGlobal.global === freeGlobal ||
                                                                          freeGlobal.window === freeGlobal ||
                                                                          freeGlobal.self === freeGlobal
                                                                      ) {
                                                                          root = freeGlobal;
                                                                      }

                                                                      /**
	 * The `punycode` object.
	 * @name punycode
	 * @type Object
	 */
                                                                      var punycode,

                                                                          /** Highest positive signed 32-bit float value */
                                                                          maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1

                                                                          /** Bootstring parameters */
                                                                          base = 36,
                                                                          tMin = 1,
                                                                          tMax = 26,
                                                                          skew = 38,
                                                                          damp = 700,
                                                                          initialBias = 72,
                                                                          initialN = 128, // 0x80
                                                                          delimiter = '-', // '\x2D'

                                                                          /** Regular expressions */
                                                                          regexPunycode = /^xn--/,
                                                                          regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
                                                                          regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators

                                                                          /** Error messages */
                                                                          errors = {
                                                                              'overflow': 'Overflow: input needs wider integers to process',
                                                                              'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
                                                                              'invalid-input': 'Invalid input'
                                                                          },

                                                                          /** Convenience shortcuts */
                                                                          baseMinusTMin = base - tMin,
                                                                          floor = Math.floor,
                                                                          stringFromCharCode = String.fromCharCode,

                                                                          /** Temporary variable */
                                                                          key;

                                                                      /*--------------------------------------------------------------------------*/

                                                                      /**
	 * A generic error utility function.
	 * @private
	 * @param {String} type The error type.
	 * @returns {Error} Throws a `RangeError` with the applicable error message.
	 */
                                                                      function error(type) {
                                                                          throw new RangeError(errors[type]);
                                                                      }

                                                                      /**
	 * A generic `Array#map` utility function.
	 * @private
	 * @param {Array} array The array to iterate over.
	 * @param {Function} callback The function that gets called for every array
	 * item.
	 * @returns {Array} A new array of values returned by the callback function.
	 */
                                                                      function map(array, fn) {
                                                                          var length = array.length;
                                                                          var result = [];
                                                                          while (length--) {
                                                                              result[length] = fn(array[length]);
                                                                          }
                                                                          return result;
                                                                      }

                                                                      /**
	 * A simple `Array#map`-like wrapper to work with domain name strings or email
	 * addresses.
	 * @private
	 * @param {String} domain The domain name or email address.
	 * @param {Function} callback The function that gets called for every
	 * character.
	 * @returns {Array} A new string of characters returned by the callback
	 * function.
	 */
                                                                      function mapDomain(string, fn) {
                                                                          var parts = string.split('@');
                                                                          var result = '';
                                                                          if (parts.length > 1) {
                                                                              // In email addresses, only the domain name should be punycoded. Leave
                                                                              // the local part (i.e. everything up to `@`) intact.
                                                                              result = parts[0] + '@';
                                                                              string = parts[1];
                                                                          }
                                                                          // Avoid `split(regex)` for IE8 compatibility. See #17.
                                                                          string = string.replace(regexSeparators, '\x2E');
                                                                          var labels = string.split('.');
                                                                          var encoded = map(labels, fn).join('.');
                                                                          return result + encoded;
                                                                      }

                                                                      /**
	 * Creates an array containing the numeric code points of each Unicode
	 * character in the string. While JavaScript uses UCS-2 internally,
	 * this function will convert a pair of surrogate halves (each of which
	 * UCS-2 exposes as separate characters) into a single code point,
	 * matching UTF-16.
	 * @see `punycode.ucs2.encode`
	 * @see <https://mathiasbynens.be/notes/javascript-encoding>
	 * @memberOf punycode.ucs2
	 * @name decode
	 * @param {String} string The Unicode input string (UCS-2).
	 * @returns {Array} The new array of code points.
	 */
                                                                      function ucs2decode(string) {
                                                                          var output = [],
                                                                              counter = 0,
                                                                              length = string.length,
                                                                              value,
                                                                              extra;
                                                                          while (counter < length) {
                                                                              value = string.charCodeAt(counter++);
                                                                              if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
                                                                                  // high surrogate, and there is a next character
                                                                                  extra = string.charCodeAt(counter++);
                                                                                  if ((extra & 0xFC00) == 0xDC00) { // low surrogate
                                                                                      output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
                                                                                  } else {
                                                                                      // unmatched surrogate; only append this code unit, in case the next
                                                                                      // code unit is the high surrogate of a surrogate pair
                                                                                      output.push(value);
                                                                                      counter--;
                                                                                  }
                                                                              } else {
                                                                                  output.push(value);
                                                                              }
                                                                          }
                                                                          return output;
                                                                      }

                                                                      /**
	 * Creates a string based on an array of numeric code points.
	 * @see `punycode.ucs2.decode`
	 * @memberOf punycode.ucs2
	 * @name encode
	 * @param {Array} codePoints The array of numeric code points.
	 * @returns {String} The new Unicode string (UCS-2).
	 */
                                                                      function ucs2encode(array) {
                                                                          return map(array, function(value) {
                                                                              var output = '';
                                                                              if (value > 0xFFFF) {
                                                                                  value -= 0x10000;
                                                                                  output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
                                                                                  value = 0xDC00 | value & 0x3FF;
                                                                              }
                                                                              output += stringFromCharCode(value);
                                                                              return output;
                                                                          }).join('');
                                                                      }

                                                                      /**
	 * Converts a basic code point into a digit/integer.
	 * @see `digitToBasic()`
	 * @private
	 * @param {Number} codePoint The basic numeric code point value.
	 * @returns {Number} The numeric value of a basic code point (for use in
	 * representing integers) in the range `0` to `base - 1`, or `base` if
	 * the code point does not represent a value.
	 */
                                                                      function basicToDigit(codePoint) {
                                                                          if (codePoint - 48 < 10) {
                                                                              return codePoint - 22;
                                                                          }
                                                                          if (codePoint - 65 < 26) {
                                                                              return codePoint - 65;
                                                                          }
                                                                          if (codePoint - 97 < 26) {
                                                                              return codePoint - 97;
                                                                          }
                                                                          return base;
                                                                      }

                                                                      /**
	 * Converts a digit/integer into a basic code point.
	 * @see `basicToDigit()`
	 * @private
	 * @param {Number} digit The numeric value of a basic code point.
	 * @returns {Number} The basic code point whose value (when used for
	 * representing integers) is `digit`, which needs to be in the range
	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
	 * used; else, the lowercase form is used. The behavior is undefined
	 * if `flag` is non-zero and `digit` has no uppercase form.
	 */
                                                                      function digitToBasic(digit, flag) {
                                                                          //  0..25 map to ASCII a..z or A..Z
                                                                          // 26..35 map to ASCII 0..9
                                                                          return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
                                                                      }

                                                                      /**
	 * Bias adaptation function as per section 3.4 of RFC 3492.
	 * https://tools.ietf.org/html/rfc3492#section-3.4
	 * @private
	 */
                                                                      function adapt(delta, numPoints, firstTime) {
                                                                          var k = 0;
                                                                          delta = firstTime ? floor(delta / damp) : delta >> 1;
                                                                          delta += floor(delta / numPoints);
                                                                          for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
                                                                              delta = floor(delta / baseMinusTMin);
                                                                          }
                                                                          return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
                                                                      }

                                                                      /**
	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
	 * symbols.
	 * @memberOf punycode
	 * @param {String} input The Punycode string of ASCII-only symbols.
	 * @returns {String} The resulting string of Unicode symbols.
	 */
                                                                      function decode(input) {
                                                                          // Don't use UCS-2
                                                                          var output = [],
                                                                              inputLength = input.length,
                                                                              out,
                                                                              i = 0,
                                                                              n = initialN,
                                                                              bias = initialBias,
                                                                              basic,
                                                                              j,
                                                                              index,
                                                                              oldi,
                                                                              w,
                                                                              k,
                                                                              digit,
                                                                              t,
                                                                              /** Cached calculation results */
                                                                              baseMinusT;

                                                                          // Handle the basic code points: let `basic` be the number of input code
                                                                          // points before the last delimiter, or `0` if there is none, then copy
                                                                          // the first basic code points to the output.

                                                                          basic = input.lastIndexOf(delimiter);
                                                                          if (basic < 0) {
                                                                              basic = 0;
                                                                          }

                                                                          for (j = 0; j < basic; ++j) {
                                                                              // if it's not a basic code point
                                                                              if (input.charCodeAt(j) >= 0x80) {
                                                                                  error('not-basic');
                                                                              }
                                                                              output.push(input.charCodeAt(j));
                                                                          }

                                                                          // Main decoding loop: start just after the last delimiter if any basic code
                                                                          // points were copied; start at the beginning otherwise.

                                                                          for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {

                                                                              // `index` is the index of the next character to be consumed.
                                                                              // Decode a generalized variable-length integer into `delta`,
                                                                              // which gets added to `i`. The overflow checking is easier
                                                                              // if we increase `i` as we go, then subtract off its starting
                                                                              // value at the end to obtain `delta`.
                                                                              for (oldi = i, w = 1, k = base; /* no condition */; k += base) {

                                                                                  if (index >= inputLength) {
                                                                                      error('invalid-input');
                                                                                  }

                                                                                  digit = basicToDigit(input.charCodeAt(index++));

                                                                                  if (digit >= base || digit > floor((maxInt - i) / w)) {
                                                                                      error('overflow');
                                                                                  }

                                                                                  i += digit * w;
                                                                                  t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);

                                                                                  if (digit < t) {
                                                                                      break;
                                                                                  }

                                                                                  baseMinusT = base - t;
                                                                                  if (w > floor(maxInt / baseMinusT)) {
                                                                                      error('overflow');
                                                                                  }

                                                                                  w *= baseMinusT;

                                                                              }

                                                                              out = output.length + 1;
                                                                              bias = adapt(i - oldi, out, oldi == 0);

                                                                              // `i` was supposed to wrap around from `out` to `0`,
                                                                              // incrementing `n` each time, so we'll fix that now:
                                                                              if (floor(i / out) > maxInt - n) {
                                                                                  error('overflow');
                                                                              }

                                                                              n += floor(i / out);
                                                                              i %= out;

                                                                              // Insert `n` at position `i` of the output
                                                                              output.splice(i++, 0, n);

                                                                          }

                                                                          return ucs2encode(output);
                                                                      }

                                                                      /**
	 * Converts a string of Unicode symbols (e.g. a domain name label) to a
	 * Punycode string of ASCII-only symbols.
	 * @memberOf punycode
	 * @param {String} input The string of Unicode symbols.
	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
	 */
                                                                      function encode(input) {
                                                                          var n,
                                                                              delta,
                                                                              handledCPCount,
                                                                              basicLength,
                                                                              bias,
                                                                              j,
                                                                              m,
                                                                              q,
                                                                              k,
                                                                              t,
                                                                              currentValue,
                                                                              output = [],
                                                                              /** `inputLength` will hold the number of code points in `input`. */
                                                                              inputLength,
                                                                              /** Cached calculation results */
                                                                              handledCPCountPlusOne,
                                                                              baseMinusT,
                                                                              qMinusT;

                                                                          // Convert the input in UCS-2 to Unicode
                                                                          input = ucs2decode(input);

                                                                          // Cache the length
                                                                          inputLength = input.length;

                                                                          // Initialize the state
                                                                          n = initialN;
                                                                          delta = 0;
                                                                          bias = initialBias;

                                                                          // Handle the basic code points
                                                                          for (j = 0; j < inputLength; ++j) {
                                                                              currentValue = input[j];
                                                                              if (currentValue < 0x80) {
                                                                                  output.push(stringFromCharCode(currentValue));
                                                                              }
                                                                          }

                                                                          handledCPCount = basicLength = output.length;

                                                                          // `handledCPCount` is the number of code points that have been handled;
                                                                          // `basicLength` is the number of basic code points.

                                                                          // Finish the basic string - if it is not empty - with a delimiter
                                                                          if (basicLength) {
                                                                              output.push(delimiter);
                                                                          }

                                                                          // Main encoding loop:
                                                                          while (handledCPCount < inputLength) {

                                                                              // All non-basic code points < n have been handled already. Find the next
                                                                              // larger one:
                                                                              for (m = maxInt, j = 0; j < inputLength; ++j) {
                                                                                  currentValue = input[j];
                                                                                  if (currentValue >= n && currentValue < m) {
                                                                                      m = currentValue;
                                                                                  }
                                                                              }

                                                                              // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
                                                                              // but guard against overflow
                                                                              handledCPCountPlusOne = handledCPCount + 1;
                                                                              if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
                                                                                  error('overflow');
                                                                              }

                                                                              delta += (m - n) * handledCPCountPlusOne;
                                                                              n = m;

                                                                              for (j = 0; j < inputLength; ++j) {
                                                                                  currentValue = input[j];

                                                                                  if (currentValue < n && ++delta > maxInt) {
                                                                                      error('overflow');
                                                                                  }

                                                                                  if (currentValue == n) {
                                                                                      // Represent delta as a generalized variable-length integer
                                                                                      for (q = delta, k = base; /* no condition */; k += base) {
                                                                                          t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
                                                                                          if (q < t) {
                                                                                              break;
                                                                                          }
                                                                                          qMinusT = q - t;
                                                                                          baseMinusT = base - t;
                                                                                          output.push(
                                                                                              stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
                                                                                          );
                                                                                          q = floor(qMinusT / baseMinusT);
                                                                                      }

                                                                                      output.push(stringFromCharCode(digitToBasic(q, 0)));
                                                                                      bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
                                                                                      delta = 0;
                                                                                      ++handledCPCount;
                                                                                  }
                                                                              }

                                                                              ++delta;
                                                                              ++n;

                                                                          }
                                                                          return output.join('');
                                                                      }

                                                                      /**
	 * Converts a Punycode string representing a domain name or an email address
	 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
	 * it doesn't matter if you call it on a string that has already been
	 * converted to Unicode.
	 * @memberOf punycode
	 * @param {String} input The Punycoded domain name or email address to
	 * convert to Unicode.
	 * @returns {String} The Unicode representation of the given Punycode
	 * string.
	 */
                                                                      function toUnicode(input) {
                                                                          return mapDomain(input, function(string) {
                                                                              return regexPunycode.test(string)
                                                                                  ? decode(string.slice(4).toLowerCase())
                                                                              : string;
                                                                          });
                                                                      }

                                                                      /**
	 * Converts a Unicode string representing a domain name or an email address to
	 * Punycode. Only the non-ASCII parts of the domain name will be converted,
	 * i.e. it doesn't matter if you call it with a domain that's already in
	 * ASCII.
	 * @memberOf punycode
	 * @param {String} input The domain name or email address to convert, as a
	 * Unicode string.
	 * @returns {String} The Punycode representation of the given domain name or
	 * email address.
	 */
                                                                      function toASCII(input) {
                                                                          return mapDomain(input, function(string) {
                                                                              return regexNonASCII.test(string)
                                                                                  ? 'xn--' + encode(string)
                                                                              : string;
                                                                          });
                                                                      }

                                                                      /*--------------------------------------------------------------------------*/

                                                                      /** Define the public API */
                                                                      punycode = {
                                                                          /**
		 * A string representing the current Punycode.js version number.
		 * @memberOf punycode
		 * @type String
		 */
                                                                          'version': '1.4.1',
                                                                          /**
		 * An object of methods to convert from JavaScript's internal character
		 * representation (UCS-2) to Unicode code points, and back.
		 * @see <https://mathiasbynens.be/notes/javascript-encoding>
		 * @memberOf punycode
		 * @type Object
		 */
                                                                          'ucs2': {
                                                                              'decode': ucs2decode,
                                                                              'encode': ucs2encode
                                                                          },
                                                                          'decode': decode,
                                                                          'encode': encode,
                                                                          'toASCII': toASCII,
                                                                          'toUnicode': toUnicode
                                                                      };

                                                                      /** Expose `punycode` */
                                                                      // Some AMD build optimizers, like r.js, check for specific condition patterns
                                                                      // like the following:
                                                                      if (
                                                                          true
                                                                      ) {
                                                                          !(__WEBPACK_AMD_DEFINE_RESULT__ = (function() {
                                                                              return punycode;
                                                                          }).call(exports, __webpack_require__, exports, module),
                                                                            __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
                                                                      } else {}

                                                                  }(this));

                                                                  /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/module.js */ "./node_modules/webpack/buildin/module.js")(module), __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js")))

            /***/ }),

        /***/ "./node_modules/querystring-es3/decode.js":
        /*!************************************************!*\
  !*** ./node_modules/querystring-es3/decode.js ***!
  \************************************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";
            // Copyright Joyent, Inc. and other Node contributors.
            //
            // Permission is hereby granted, free of charge, to any person obtaining a
            // copy of this software and associated documentation files (the
            // "Software"), to deal in the Software without restriction, including
            // without limitation the rights to use, copy, modify, merge, publish,
            // distribute, sublicense, and/or sell copies of the Software, and to permit
            // persons to whom the Software is furnished to do so, subject to the
            // following conditions:
            //
            // The above copyright notice and this permission notice shall be included
            // in all copies or substantial portions of the Software.
            //
            // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
            // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
            // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
            // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
            // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
            // USE OR OTHER DEALINGS IN THE SOFTWARE.



            // If obj.hasOwnProperty has been overridden, then calling
            // obj.hasOwnProperty(prop) will break.
            // See: https://github.com/joyent/node/issues/1707
            function hasOwnProperty(obj, prop) {
                return Object.prototype.hasOwnProperty.call(obj, prop);
            }

            module.exports = function(qs, sep, eq, options) {
                sep = sep || '&';
                eq = eq || '=';
                var obj = {};

                if (typeof qs !== 'string' || qs.length === 0) {
                    return obj;
                }

                var regexp = /\+/g;
                qs = qs.split(sep);

                var maxKeys = 1000;
                if (options && typeof options.maxKeys === 'number') {
                    maxKeys = options.maxKeys;
                }

                var len = qs.length;
                // maxKeys <= 0 means that we should not limit keys count
                if (maxKeys > 0 && len > maxKeys) {
                    len = maxKeys;
                }

                for (var i = 0; i < len; ++i) {
                    var x = qs[i].replace(regexp, '%20'),
                        idx = x.indexOf(eq),
                        kstr, vstr, k, v;

                    if (idx >= 0) {
                        kstr = x.substr(0, idx);
                        vstr = x.substr(idx + 1);
                    } else {
                        kstr = x;
                        vstr = '';
                    }

                    k = decodeURIComponent(kstr);
                    v = decodeURIComponent(vstr);

                    if (!hasOwnProperty(obj, k)) {
                        obj[k] = v;
                    } else if (isArray(obj[k])) {
                        obj[k].push(v);
                    } else {
                        obj[k] = [obj[k], v];
                    }
                }

                return obj;
            };

            var isArray = Array.isArray || function (xs) {
                return Object.prototype.toString.call(xs) === '[object Array]';
            };


            /***/ }),

        /***/ "./node_modules/querystring-es3/encode.js":
        /*!************************************************!*\
  !*** ./node_modules/querystring-es3/encode.js ***!
  \************************************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";
            // Copyright Joyent, Inc. and other Node contributors.
            //
            // Permission is hereby granted, free of charge, to any person obtaining a
            // copy of this software and associated documentation files (the
            // "Software"), to deal in the Software without restriction, including
            // without limitation the rights to use, copy, modify, merge, publish,
            // distribute, sublicense, and/or sell copies of the Software, and to permit
            // persons to whom the Software is furnished to do so, subject to the
            // following conditions:
            //
            // The above copyright notice and this permission notice shall be included
            // in all copies or substantial portions of the Software.
            //
            // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
            // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
            // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
            // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
            // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
            // USE OR OTHER DEALINGS IN THE SOFTWARE.



            var stringifyPrimitive = function(v) {
                switch (typeof v) {
                    case 'string':
                        return v;

                    case 'boolean':
                        return v ? 'true' : 'false';

                    case 'number':
                        return isFinite(v) ? v : '';

                    default:
                        return '';
                }
            };

            module.exports = function(obj, sep, eq, name) {
                sep = sep || '&';
                eq = eq || '=';
                if (obj === null) {
                    obj = undefined;
                }

                if (typeof obj === 'object') {
                    return map(objectKeys(obj), function(k) {
                        var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
                        if (isArray(obj[k])) {
                            return map(obj[k], function(v) {
                                return ks + encodeURIComponent(stringifyPrimitive(v));
                            }).join(sep);
                        } else {
                            return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
                        }
                    }).join(sep);

                }

                if (!name) return '';
                return encodeURIComponent(stringifyPrimitive(name)) + eq +
                    encodeURIComponent(stringifyPrimitive(obj));
            };

            var isArray = Array.isArray || function (xs) {
                return Object.prototype.toString.call(xs) === '[object Array]';
            };

            function map (xs, f) {
                if (xs.map) return xs.map(f);
                var res = [];
                for (var i = 0; i < xs.length; i++) {
                    res.push(f(xs[i], i));
                }
                return res;
            }

            var objectKeys = Object.keys || function (obj) {
                var res = [];
                for (var key in obj) {
                    if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
                }
                return res;
            };


            /***/ }),

        /***/ "./node_modules/querystring-es3/index.js":
        /*!***********************************************!*\
  !*** ./node_modules/querystring-es3/index.js ***!
  \***********************************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";


            // exports.decode = exports.parse = __webpack_require__(/*! ./decode */ "./node_modules/querystring-es3/decode.js");
            // exports.encode = exports.stringify = __webpack_require__(/*! ./encode */ "./node_modules/querystring-es3/encode.js");


            /***/ }),

        /***/ "./node_modules/url/url.js":
        /*!*********************************!*\
  !*** ./node_modules/url/url.js ***!
  \*********************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";
            // Copyright Joyent, Inc. and other Node contributors.
            //
            // Permission is hereby granted, free of charge, to any person obtaining a
            // copy of this software and associated documentation files (the
            // "Software"), to deal in the Software without restriction, including
            // without limitation the rights to use, copy, modify, merge, publish,
            // distribute, sublicense, and/or sell copies of the Software, and to permit
            // persons to whom the Software is furnished to do so, subject to the
            // following conditions:
            //
            // The above copyright notice and this permission notice shall be included
            // in all copies or substantial portions of the Software.
            //
            // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
            // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
            // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
            // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
            // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
            // USE OR OTHER DEALINGS IN THE SOFTWARE.



            var punycode = __webpack_require__(/*! punycode */ "./node_modules/punycode/punycode.js");
            var util = __webpack_require__(/*! ./util */ "./node_modules/url/util.js");

            exports.parse = urlParse;
            exports.resolve = urlResolve;
            exports.resolveObject = urlResolveObject;
            exports.format = urlFormat;

            exports.Url = Url;

            function Url() {
                this.protocol = null;
                this.slashes = null;
                this.auth = null;
                this.host = null;
                this.port = null;
                this.hostname = null;
                this.hash = null;
                this.search = null;
                this.query = null;
                this.pathname = null;
                this.path = null;
                this.href = null;
            }

            // Reference: RFC 3986, RFC 1808, RFC 2396

            // define these here so at least they only have to be
            // compiled once on the first module load.
            var protocolPattern = /^([a-z0-9.+-]+:)/i,
                portPattern = /:[0-9]*$/,

                // Special case for a simple path URL
                simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,

                // RFC 2396: characters reserved for delimiting URLs.
                // We actually just auto-escape these.
                delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],

                // RFC 2396: characters not allowed for various reasons.
                unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),

                // Allowed by RFCs, but cause of XSS attacks.  Always escape these.
                autoEscape = ['\''].concat(unwise),
                // Characters that are never ever allowed in a hostname.
                // Note that any invalid chars are also handled, but these
                // are the ones that are *expected* to be seen, so we fast-path
                // them.
                nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
                hostEndingChars = ['/', '?', '#'],
                hostnameMaxLen = 255,
                hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,
                hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,
                // protocols that can allow "unsafe" and "unwise" chars.
                unsafeProtocol = {
                    'javascript': true,
                    'javascript:': true
                },
                // protocols that never have a hostname.
                hostlessProtocol = {
                    'javascript': true,
                    'javascript:': true
                },
                // protocols that always contain a // bit.
                slashedProtocol = {
                    'http': true,
                    'https': true,
                    'ftp': true,
                    'gopher': true,
                    'file': true,
                    'http:': true,
                    'https:': true,
                    'ftp:': true,
                    'gopher:': true,
                    'file:': true
                },
                querystring = __webpack_require__(/*! querystring */ "./node_modules/querystring-es3/index.js");

            function urlParse(url, parseQueryString, slashesDenoteHost) {
                if (url && util.isObject(url) && url instanceof Url) return url;

                var u = new Url;
                u.parse(url, parseQueryString, slashesDenoteHost);
                return u;
            }

            Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
                if (!util.isString(url)) {
                    throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
                }

                // Copy chrome, IE, opera backslash-handling behavior.
                // Back slashes before the query string get converted to forward slashes
                // See: https://code.google.com/p/chromium/issues/detail?id=25916
                var queryIndex = url.indexOf('?'),
                    splitter =
                    (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',
                    uSplit = url.split(splitter),
                    slashRegex = /\\/g;
                uSplit[0] = uSplit[0].replace(slashRegex, '/');
                url = uSplit.join(splitter);

                var rest = url;

                // trim before proceeding.
                // This is to support parse stuff like "  http://foo.com  \n"
                rest = rest.trim();

                if (!slashesDenoteHost && url.split('#').length === 1) {
                    // Try fast path regexp
                    var simplePath = simplePathPattern.exec(rest);
                    if (simplePath) {
                        this.path = rest;
                        this.href = rest;
                        this.pathname = simplePath[1];
                        if (simplePath[2]) {
                            this.search = simplePath[2];
                            if (parseQueryString) {
                                this.query = querystring.parse(this.search.substr(1));
                            } else {
                                this.query = this.search.substr(1);
                            }
                        } else if (parseQueryString) {
                            this.search = '';
                            this.query = {};
                        }
                        return this;
                    }
                }

                var proto = protocolPattern.exec(rest);
                if (proto) {
                    proto = proto[0];
                    var lowerProto = proto.toLowerCase();
                    this.protocol = lowerProto;
                    rest = rest.substr(proto.length);
                }

                // figure out if it's got a host
                // user@server is *always* interpreted as a hostname, and url
                // resolution will treat //foo/bar as host=foo,path=bar because that's
                // how the browser resolves relative URLs.
                if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
                    var slashes = rest.substr(0, 2) === '//';
                    if (slashes && !(proto && hostlessProtocol[proto])) {
                        rest = rest.substr(2);
                        this.slashes = true;
                    }
                }

                if (!hostlessProtocol[proto] &&
                    (slashes || (proto && !slashedProtocol[proto]))) {

                    // there's a hostname.
                    // the first instance of /, ?, ;, or # ends the host.
                    //
                    // If there is an @ in the hostname, then non-host chars *are* allowed
                    // to the left of the last @ sign, unless some host-ending character
                    // comes *before* the @-sign.
                    // URLs are obnoxious.
                    //
                    // ex:
                    // http://a@b@c/ => user:a@b host:c
                    // http://a@b?@c => user:a host:c path:/?@c

                    // v0.12 TODO(isaacs): This is not quite how Chrome does things.
                    // Review our test case against browsers more comprehensively.

                    // find the first instance of any hostEndingChars
                    var hostEnd = -1;
                    for (var i = 0; i < hostEndingChars.length; i++) {
                        var hec = rest.indexOf(hostEndingChars[i]);
                        if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
                            hostEnd = hec;
                    }

                    // at this point, either we have an explicit point where the
                    // auth portion cannot go past, or the last @ char is the decider.
                    var auth, atSign;
                    if (hostEnd === -1) {
                        // atSign can be anywhere.
                        atSign = rest.lastIndexOf('@');
                    } else {
                        // atSign must be in auth portion.
                        // http://a@b/c@d => host:b auth:a path:/c@d
                        atSign = rest.lastIndexOf('@', hostEnd);
                    }

                    // Now we have a portion which is definitely the auth.
                    // Pull that off.
                    if (atSign !== -1) {
                        auth = rest.slice(0, atSign);
                        rest = rest.slice(atSign + 1);
                        this.auth = decodeURIComponent(auth);
                    }

                    // the host is the remaining to the left of the first non-host char
                    hostEnd = -1;
                    for (var i = 0; i < nonHostChars.length; i++) {
                        var hec = rest.indexOf(nonHostChars[i]);
                        if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
                            hostEnd = hec;
                    }
                    // if we still have not hit it, then the entire thing is a host.
                    if (hostEnd === -1)
                        hostEnd = rest.length;

                    this.host = rest.slice(0, hostEnd);
                    rest = rest.slice(hostEnd);

                    // pull out port.
                    this.parseHost();

                    // we've indicated that there is a hostname,
                    // so even if it's empty, it has to be present.
                    this.hostname = this.hostname || '';

                    // if hostname begins with [ and ends with ]
                    // assume that it's an IPv6 address.
                    var ipv6Hostname = this.hostname[0] === '[' &&
                        this.hostname[this.hostname.length - 1] === ']';

                    // validate a little.
                    if (!ipv6Hostname) {
                        var hostparts = this.hostname.split(/\./);
                        for (var i = 0, l = hostparts.length; i < l; i++) {
                            var part = hostparts[i];
                            if (!part) continue;
                            if (!part.match(hostnamePartPattern)) {
                                var newpart = '';
                                for (var j = 0, k = part.length; j < k; j++) {
                                    if (part.charCodeAt(j) > 127) {
                                        // we replace non-ASCII char with a temporary placeholder
                                        // we need this to make sure size of hostname is not
                                        // broken by replacing non-ASCII by nothing
                                        newpart += 'x';
                                    } else {
                                        newpart += part[j];
                                    }
                                }
                                // we test again with ASCII char only
                                if (!newpart.match(hostnamePartPattern)) {
                                    var validParts = hostparts.slice(0, i);
                                    var notHost = hostparts.slice(i + 1);
                                    var bit = part.match(hostnamePartStart);
                                    if (bit) {
                                        validParts.push(bit[1]);
                                        notHost.unshift(bit[2]);
                                    }
                                    if (notHost.length) {
                                        rest = '/' + notHost.join('.') + rest;
                                    }
                                    this.hostname = validParts.join('.');
                                    break;
                                }
                            }
                        }
                    }

                    if (this.hostname.length > hostnameMaxLen) {
                        this.hostname = '';
                    } else {
                        // hostnames are always lower case.
                        this.hostname = this.hostname.toLowerCase();
                    }

                    if (!ipv6Hostname) {
                        // IDNA Support: Returns a punycoded representation of "domain".
                        // It only converts parts of the domain name that
                        // have non-ASCII characters, i.e. it doesn't matter if
                        // you call it with a domain that already is ASCII-only.
                        this.hostname = punycode.toASCII(this.hostname);
                    }

                    var p = this.port ? ':' + this.port : '';
                    var h = this.hostname || '';
                    this.host = h + p;
                    this.href += this.host;

                    // strip [ and ] from the hostname
                    // the host field still retains them, though
                    if (ipv6Hostname) {
                        this.hostname = this.hostname.substr(1, this.hostname.length - 2);
                        if (rest[0] !== '/') {
                            rest = '/' + rest;
                        }
                    }
                }

                // now rest is set to the post-host stuff.
                // chop off any delim chars.
                if (!unsafeProtocol[lowerProto]) {

                    // First, make 100% sure that any "autoEscape" chars get
                    // escaped, even if encodeURIComponent doesn't think they
                    // need to be.
                    for (var i = 0, l = autoEscape.length; i < l; i++) {
                        var ae = autoEscape[i];
                        if (rest.indexOf(ae) === -1)
                            continue;
                        var esc = encodeURIComponent(ae);
                        if (esc === ae) {
                            esc = escape(ae);
                        }
                        rest = rest.split(ae).join(esc);
                    }
                }


                // chop off from the tail first.
                var hash = rest.indexOf('#');
                if (hash !== -1) {
                    // got a fragment string.
                    this.hash = rest.substr(hash);
                    rest = rest.slice(0, hash);
                }
                var qm = rest.indexOf('?');
                if (qm !== -1) {
                    this.search = rest.substr(qm);
                    this.query = rest.substr(qm + 1);
                    if (parseQueryString && querystring.parse) {
                        this.query = querystring.parse(this.query);
                    }
                    rest = rest.slice(0, qm);
                } else if (parseQueryString) {
                    // no query string, but parseQueryString still requested
                    this.search = '';
                    this.query = {};
                }
                if (rest) this.pathname = rest;
                if (slashedProtocol[lowerProto] &&
                    this.hostname && !this.pathname) {
                    this.pathname = '/';
                }

                //to support http.request
                if (this.pathname || this.search) {
                    var p = this.pathname || '';
                    var s = this.search || '';
                    this.path = p + s;
                }

                // finally, reconstruct the href based on what has been validated.
                this.href = this.format();
                return this;
            };

            // format a parsed object into a url string
            function urlFormat(obj) {
                // ensure it's an object, and not a string url.
                // If it's an obj, this is a no-op.
                // this way, you can call url_format() on strings
                // to clean up potentially wonky urls.
                if (util.isString(obj)) obj = urlParse(obj);
                if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
                return obj.format();
            }

            Url.prototype.format = function() {
                var auth = this.auth || '';
                if (auth) {
                    auth = encodeURIComponent(auth);
                    auth = auth.replace(/%3A/i, ':');
                    auth += '@';
                }

                var protocol = this.protocol || '',
                    pathname = this.pathname || '',
                    hash = this.hash || '',
                    host = false,
                    query = '';

                if (this.host) {
                    host = auth + this.host;
                } else if (this.hostname) {
                    host = auth + (this.hostname.indexOf(':') === -1 ?
                                   this.hostname :
                                   '[' + this.hostname + ']');
                    if (this.port) {
                        host += ':' + this.port;
                    }
                }

                if (this.query &&
                    util.isObject(this.query) &&
                    Object.keys(this.query).length) {
                    query = querystring.stringify(this.query);
                }

                var search = this.search || (query && ('?' + query)) || '';

                if (protocol && protocol.substr(-1) !== ':') protocol += ':';

                // only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.
                // unless they had them to begin with.
                if (this.slashes ||
                    (!protocol || slashedProtocol[protocol]) && host !== false) {
                    host = '//' + (host || '');
                    if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
                } else if (!host) {
                    host = '';
                }

                if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
                if (search && search.charAt(0) !== '?') search = '?' + search;

                pathname = pathname.replace(/[?#]/g, function(match) {
                    return encodeURIComponent(match);
                });
                search = search.replace('#', '%23');

                return protocol + host + pathname + search + hash;
            };

            function urlResolve(source, relative) {
                return urlParse(source, false, true).resolve(relative);
            }

            Url.prototype.resolve = function(relative) {
                return this.resolveObject(urlParse(relative, false, true)).format();
            };

            function urlResolveObject(source, relative) {
                if (!source) return relative;
                return urlParse(source, false, true).resolveObject(relative);
            }

            Url.prototype.resolveObject = function(relative) {
                if (util.isString(relative)) {
                    var rel = new Url();
                    rel.parse(relative, false, true);
                    relative = rel;
                }

                var result = new Url();
                var tkeys = Object.keys(this);
                for (var tk = 0; tk < tkeys.length; tk++) {
                    var tkey = tkeys[tk];
                    result[tkey] = this[tkey];
                }

                // hash is always overridden, no matter what.
                // even href="" will remove it.
                result.hash = relative.hash;

                // if the relative url is empty, then there's nothing left to do here.
                if (relative.href === '') {
                    result.href = result.format();
                    return result;
                }

                // hrefs like //foo/bar always cut to the protocol.
                if (relative.slashes && !relative.protocol) {
                    // take everything except the protocol from relative
                    var rkeys = Object.keys(relative);
                    for (var rk = 0; rk < rkeys.length; rk++) {
                        var rkey = rkeys[rk];
                        if (rkey !== 'protocol')
                            result[rkey] = relative[rkey];
                    }

                    //urlParse appends trailing / to urls like http://www.example.com
                    if (slashedProtocol[result.protocol] &&
                        result.hostname && !result.pathname) {
                        result.path = result.pathname = '/';
                    }

                    result.href = result.format();
                    return result;
                }

                if (relative.protocol && relative.protocol !== result.protocol) {
                    // if it's a known url protocol, then changing
                    // the protocol does weird things
                    // first, if it's not file:, then we MUST have a host,
                    // and if there was a path
                    // to begin with, then we MUST have a path.
                    // if it is file:, then the host is dropped,
                    // because that's known to be hostless.
                    // anything else is assumed to be absolute.
                    if (!slashedProtocol[relative.protocol]) {
                        var keys = Object.keys(relative);
                        for (var v = 0; v < keys.length; v++) {
                            var k = keys[v];
                            result[k] = relative[k];
                        }
                        result.href = result.format();
                        return result;
                    }

                    result.protocol = relative.protocol;
                    if (!relative.host && !hostlessProtocol[relative.protocol]) {
                        var relPath = (relative.pathname || '').split('/');
                        while (relPath.length && !(relative.host = relPath.shift()));
                        if (!relative.host) relative.host = '';
                        if (!relative.hostname) relative.hostname = '';
                        if (relPath[0] !== '') relPath.unshift('');
                        if (relPath.length < 2) relPath.unshift('');
                        result.pathname = relPath.join('/');
                    } else {
                        result.pathname = relative.pathname;
                    }
                    result.search = relative.search;
                    result.query = relative.query;
                    result.host = relative.host || '';
                    result.auth = relative.auth;
                    result.hostname = relative.hostname || relative.host;
                    result.port = relative.port;
                    // to support http.request
                    if (result.pathname || result.search) {
                        var p = result.pathname || '';
                        var s = result.search || '';
                        result.path = p + s;
                    }
                    result.slashes = result.slashes || relative.slashes;
                    result.href = result.format();
                    return result;
                }

                var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
                    isRelAbs = (
                        relative.host ||
                        relative.pathname && relative.pathname.charAt(0) === '/'
                    ),
                    mustEndAbs = (isRelAbs || isSourceAbs ||
                                  (result.host && relative.pathname)),
                    removeAllDots = mustEndAbs,
                    srcPath = result.pathname && result.pathname.split('/') || [],
                    relPath = relative.pathname && relative.pathname.split('/') || [],
                    psychotic = result.protocol && !slashedProtocol[result.protocol];

                // if the url is a non-slashed url, then relative
                // links like ../.. should be able
                // to crawl up to the hostname, as well.  This is strange.
                // result.protocol has already been set by now.
                // Later on, put the first path part into the host field.
                if (psychotic) {
                    result.hostname = '';
                    result.port = null;
                    if (result.host) {
                        if (srcPath[0] === '') srcPath[0] = result.host;
                        else srcPath.unshift(result.host);
                    }
                    result.host = '';
                    if (relative.protocol) {
                        relative.hostname = null;
                        relative.port = null;
                        if (relative.host) {
                            if (relPath[0] === '') relPath[0] = relative.host;
                            else relPath.unshift(relative.host);
                        }
                        relative.host = null;
                    }
                    mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
                }

                if (isRelAbs) {
                    // it's absolute.
                    result.host = (relative.host || relative.host === '') ?
                        relative.host : result.host;
                    result.hostname = (relative.hostname || relative.hostname === '') ?
                        relative.hostname : result.hostname;
                    result.search = relative.search;
                    result.query = relative.query;
                    srcPath = relPath;
                    // fall through to the dot-handling below.
                } else if (relPath.length) {
                    // it's relative
                    // throw away the existing file, and take the new path instead.
                    if (!srcPath) srcPath = [];
                    srcPath.pop();
                    srcPath = srcPath.concat(relPath);
                    result.search = relative.search;
                    result.query = relative.query;
                } else if (!util.isNullOrUndefined(relative.search)) {
                    // just pull out the search.
                    // like href='?foo'.
                    // Put this after the other two cases because it simplifies the booleans
                    if (psychotic) {
                        result.hostname = result.host = srcPath.shift();
                        //occationaly the auth can get stuck only in host
                        //this especially happens in cases like
                        //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
                        var authInHost = result.host && result.host.indexOf('@') > 0 ?
                            result.host.split('@') : false;
                        if (authInHost) {
                            result.auth = authInHost.shift();
                            result.host = result.hostname = authInHost.shift();
                        }
                    }
                    result.search = relative.search;
                    result.query = relative.query;
                    //to support http.request
                    if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
                        result.path = (result.pathname ? result.pathname : '') +
                            (result.search ? result.search : '');
                    }
                    result.href = result.format();
                    return result;
                }

                if (!srcPath.length) {
                    // no path at all.  easy.
                    // we've already handled the other stuff above.
                    result.pathname = null;
                    //to support http.request
                    if (result.search) {
                        result.path = '/' + result.search;
                    } else {
                        result.path = null;
                    }
                    result.href = result.format();
                    return result;
                }

                // if a url ENDs in . or .., then it must get a trailing slash.
                // however, if it ends in anything else non-slashy,
                // then it must NOT get a trailing slash.
                var last = srcPath.slice(-1)[0];
                var hasTrailingSlash = (
                    (result.host || relative.host || srcPath.length > 1) &&
                    (last === '.' || last === '..') || last === '');

                // strip single dots, resolve double dots to parent dir
                // if the path tries to go above the root, `up` ends up > 0
                var up = 0;
                for (var i = srcPath.length; i >= 0; i--) {
                    last = srcPath[i];
                    if (last === '.') {
                        srcPath.splice(i, 1);
                    } else if (last === '..') {
                        srcPath.splice(i, 1);
                        up++;
                    } else if (up) {
                        srcPath.splice(i, 1);
                        up--;
                    }
                }

                // if the path is allowed to go above the root, restore leading ..s
                if (!mustEndAbs && !removeAllDots) {
                    for (; up--; up) {
                        srcPath.unshift('..');
                    }
                }

                if (mustEndAbs && srcPath[0] !== '' &&
                    (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
                    srcPath.unshift('');
                }

                if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
                    srcPath.push('');
                }

                var isAbsolute = srcPath[0] === '' ||
                    (srcPath[0] && srcPath[0].charAt(0) === '/');

                // put the host back
                if (psychotic) {
                    result.hostname = result.host = isAbsolute ? '' :
                    srcPath.length ? srcPath.shift() : '';
                    //occationaly the auth can get stuck only in host
                    //this especially happens in cases like
                    //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
                    var authInHost = result.host && result.host.indexOf('@') > 0 ?
                        result.host.split('@') : false;
                    if (authInHost) {
                        result.auth = authInHost.shift();
                        result.host = result.hostname = authInHost.shift();
                    }
                }

                mustEndAbs = mustEndAbs || (result.host && srcPath.length);

                if (mustEndAbs && !isAbsolute) {
                    srcPath.unshift('');
                }

                if (!srcPath.length) {
                    result.pathname = null;
                    result.path = null;
                } else {
                    result.pathname = srcPath.join('/');
                }

                //to support request.http
                if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
                    result.path = (result.pathname ? result.pathname : '') +
                        (result.search ? result.search : '');
                }
                result.auth = relative.auth || result.auth;
                result.slashes = result.slashes || relative.slashes;
                result.href = result.format();
                return result;
            };

            Url.prototype.parseHost = function() {
                var host = this.host;
                var port = portPattern.exec(host);
                if (port) {
                    port = port[0];
                    if (port !== ':') {
                        this.port = port.substr(1);
                    }
                    host = host.substr(0, host.length - port.length);
                }
                if (host) this.hostname = host;
            };


            /***/ }),

        /***/ "./node_modules/url/util.js":
        /*!**********************************!*\
  !*** ./node_modules/url/util.js ***!
  \**********************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";


            module.exports = {
                isString: function(arg) {
                    return typeof(arg) === 'string';
                },
                isObject: function(arg) {
                    return typeof(arg) === 'object' && arg !== null;
                },
                isNull: function(arg) {
                    return arg === null;
                },
                isNullOrUndefined: function(arg) {
                    return arg == null;
                }
            };


            /***/ }),

        /***/ "./node_modules/webpack/buildin/global.js":
        /*!***********************************!*\
  !*** (webpack)/buildin/global.js ***!
  \***********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            var g;

            // This works in non-strict mode
            g = (function() {
                return this;
            })();

            try {
                // This works if eval is allowed (see CSP)
                g = g || new Function("return this")();
            } catch (e) {
                // This works if the window reference is available
                if (typeof window === "object") g = window;
            }

            // g can still be undefined, but nothing to do about it...
            // We return undefined, instead of nothing here, so it's
            // easier to handle this case. if(!global) { ...}

            module.exports = g;


            /***/ }),

        /***/ "./node_modules/webpack/buildin/module.js":
        /*!***********************************!*\
  !*** (webpack)/buildin/module.js ***!
  \***********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            module.exports = function(module) {
                if (!module.webpackPolyfill) {
                    module.deprecate = function() {};
                    module.paths = [];
                    // module.parent = undefined by default
                    if (!module.children) module.children = [];
                    Object.defineProperty(module, "loaded", {
                        enumerable: true,
                        get: function() {
                            return module.l;
                        }
                    });
                    Object.defineProperty(module, "id", {
                        enumerable: true,
                        get: function() {
                            return module.i;
                        }
                    });
                    module.webpackPolyfill = 1;
                }
                return module;
            };


            /***/ }),

        /***/ "./src/js/app.js":
        /*!***********************!*\
  !*** ./src/js/app.js ***!
  \***********************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            "use strict";


            window.loadedScript = true;

            // ENV:
            var isProd = location.hostname !== "127.0.0.1" && !location.hostname.startsWith("192.168.");

            // IMPORTS:
            __webpack_require__(/*! ./libs/modernizr.js */ "./src/js/libs/modernizr.js");
            var io = __webpack_require__(/*! ./libs/io-client.js */ "./src/js/libs/io-client.js");
            var UTILS = __webpack_require__(/*! ./libs/utils.js */ "./src/js/libs/utils.js");
            var animText = __webpack_require__(/*! ./libs/animText.js */ "./src/js/libs/animText.js");
            var config = __webpack_require__(/*! ./config.js */ "./src/js/config.js");
            var GameObject = __webpack_require__(/*! ./data/gameObject.js */ "./src/js/data/gameObject.js");
            var items = __webpack_require__(/*! ./data/items.js */ "./src/js/data/items.js");
            var MapManager = __webpack_require__(/*! ./data/mapManager.js */ "./src/js/data/mapManager.js");
            var ObjectManager = __webpack_require__(/*! ./data/objectManager.js */ "./src/js/data/objectManager.js");
            var Player = __webpack_require__(/*! ./data/player.js */ "./src/js/data/player.js");
            var store = __webpack_require__(/*! ./data/store.js */ "./src/js/data/store.js");
            var Projectile = __webpack_require__(/*! ./data/projectile.js */ "./src/js/data/projectile.js");
            var ProjectileManager = __webpack_require__(/*! ./data/projectileManager.js */ "./src/js/data/projectileManager.js");
            var SoundManager = __webpack_require__(/*! ./libs/soundManager.js */ "./src/js/libs/soundManager.js").obj;
            var textManager = new animText.TextManager();

            // VULTR:
            //var VultrClient = null//__webpack_require__(/*! ../../vultr/VultrClient.js */ "./vultr/VultrClient.js");
            //var vultrClient = new VultrClient("moomoo.io", 3000, config.maxPlayers, 5, false);
            //vultrClient.debugLog = false;

            // URL PARAMS:
            function getParameterByName(name, url) {
                if (!url) {
                    url = window.location.href;
                }
                name = name.replace(/[\[\]]/g, "\\$&");
                var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
                    results = regex.exec(url);
                if (!results) return null;
                if (!results[2]) return '';
                return decodeURIComponent(results[2].replace(/\+/g, " "));
            }

            // SOCKET & CONNECTION:
            var connected = false;
            var startedConnecting = false;
            function connectSocketIfReady(server) {
                if(server) bestServer = server;

                // MAKE SURE IT'S READY:
                if (!didLoad || !captchaReady || !bestServer) return;
                startedConnecting = true;

                // GET TOKEN:
                if (isProd) {
                    /*window.turnstileToken ? connectSocket(window.turnstileToken) : */window.grecaptcha
                        .execute("6LfahtgjAAAAAF8SkpjyeYMcxMdxIaQeh-VoPATP", {
                        action: "homepage",
                    })
                        .then(function(token) {
                        // CONNECT SOCKET:
                        connectSocket(`re:${token}`, server);
                    })
                } else {
                    // CONNECT SOCKET:
                    connectSocket(null);
                }
            }
            function connectSocket(token, server) {
                if(server) bestServer = server;

                // CONNECT SOCKET:
                let ip = `${bestServer.key}.${bestServer.region}`;

                // CREATE ADDRESS:
                let wsAddress = `wss://${ip}.moomoo.io/`
			 if (token) wsAddress += "?token=" + encodeURIComponent(token);

                usedServer = bestServer;
                // CONNECT:
                io.connect(wsAddress, function(error) {
                    pingSocket();

                    setInterval(pingSocket, 500);

                    if (error) {
                        startedConnecting = false;
                        disconnect(error);
                    } else {
                        connected = true;
                        startGame();
                    }
                }, {
                    "A": setInitData,
                    "B": disconnect,
                    "C": setupGame,
                    "D": addPlayer,
                    "E": removePlayer,
                    "a": updatePlayers,
                    "G": updateLeaderboard,
                    "H": loadGameObject,
                    "I": loadAI,
                    "J": animateAI,
                    "K": gatherAnimation,
                    "L": wiggleGameObject,
                    "M": shootTurret,
                    "N": updatePlayerValue,
                    "O": updateHealth,
                    "P": killPlayer,
                    "Q": killObject,
                    "R": killObjects,
                    "S": updateItemCounts,
                    "T": updateAge,
                    "U": updateUpgrades,
                    "V": updateItems,
                    "X": addProjectile,
                    "Y": remProjectile,
                    "Z": serverShutdownNotice,
                    "g": addAlliance,
                    "1": deleteAlliance,
                    "2": allianceNotification,
                    "3": setPlayerTeam,
                    "4": setAlliancePlayers,
                    "5": updateStoreItems,
                    "6": receiveChat,
                    "7": updateMinimap,
                    "8": showText,
                    "9": pingMap,
                    "0": pingSocketResponse
                });
            }
            function socketReady() {
                return (io.connected);
            }
            function joinParty() {
                var currentKey = serverBrowser.value;
                var key = prompt("party key", currentKey);
                if (key) {
                    window.onbeforeunload = undefined; // Don't ask to leave
                    window.location.href = "/?server=" + key;
                }
            }/**/

            // SOUND:
            var Sound = new SoundManager(config, UTILS);
            function toggleSound(active) {
                if (active == undefined)
                    active = !Sound.active;
                Sound.active = active;
                //Sound.toggleMute("menu", !active);
                saveVal("moo_moosic", active?1:0);
            }

            // MATHS:
            var mathPI = Math.PI;
            var mathPI2 = mathPI * 2;
            var mathPI3 = mathPI * 3;
            Math.lerpAngle = function (value1, value2, amount) {
                var difference = Math.abs(value2 - value1);
                if (difference > mathPI) {
                    if (value1 > value2) {
                        value2 += mathPI2;
                    } else {
                        value1 += mathPI2;
                    }
                }
                var value = (value2 + ((value1 - value2) * amount));
                if (value >= 0 && value <= mathPI2)
                    return value;
                return (value % mathPI2);
            }

            // REOUNDED RECTANGLE:
            CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
                if (w < 2 * r) r = w / 2;
                if (h < 2 * r) r = h / 2;
                if (r < 0)
                    r = 0;
                this.beginPath();
                this.moveTo(x+r, y);
                this.arcTo(x+w, y, x+w, y+h, r);
                this.arcTo(x+w, y+h, x, y+h, r);
                this.arcTo(x, y+h, x, y, r);
                this.arcTo(x, y, x+w, y, r);
                this.closePath();
                return this;
            }

            // STORAGE:
            var canStore;
            if (typeof(Storage) !== "undefined") {
                canStore = true;
            }
            function saveVal(name, val) {
                if (canStore)
                    localStorage.setItem(name, val);
            }
            function deleteVal(name) {
                if (canStore)
                    localStorage.removeItem(name);
            }
            function getSavedVal(name) {
                if (canStore)
                    return localStorage.getItem(name);
                return null;
            }

            // TERMS:

            window.checkTerms = function(yes) {
                if (yes) {
                    consentBlock.style.display = "none";
                    saveVal("consent", 1);
                } else $("#consentShake").effect("shake");
            };

            // GLOBAL VALUES:
            var moofoll = getSavedVal("moofoll");
            function follmoo() {
                if (!moofoll) {
                    moofoll = true;
                    saveVal("moofoll", 1);
                }
            }
            var useNativeResolution;
            var showPing;
            var playSound;
            var pixelDensity = 1;
            var delta, now, lastSent;
            var lastUpdate = Date.now();
            var keys, attackState;
            var ais = [];
            var players = [];
            var alliances = [];
            var gameObjects = [];
            var projectiles = [];
            var projectileManager = new ProjectileManager(Projectile, projectiles, players, ais, objectManager, items, config, UTILS);
            var AiManager = __webpack_require__(/*! ./data/aiManager.js */ "./src/js/data/aiManager.js");
            var AI = __webpack_require__(/*! ./data/ai.js */ "./src/js/data/ai.js");
            var aiManager = new AiManager(ais, AI, players, items, null, config, UTILS);
            var player, playerSID, tmpObj;
            var waterMult = 1;
            var waterPlus = 0;
            var mouseX = 0;
            var mouseY = 0;
            var controllingTouch = {
                id: -1,
                startX: 0,
                startY: 0,
                currentX: 0,
                currentY: 0
            };
            var attackingTouch = {
                id: -1,
                startX: 0,
                startY: 0,
                currentX: 0,
                currentY: 0
            };
            var camX, camY;
            var tmpDir;
            var skinColor = 0;
            var maxScreenWidth = config.maxScreenWidth;
            var maxScreenHeight = config.maxScreenHeight;
            var screenWidth, screenHeight;
            var inGame = false;
            var adContainer = document.getElementById("ad-container");
            var mainMenu = document.getElementById("mainMenu");
            var enterGameButton = document.getElementById("enterGame");
            var promoImageButton = document.getElementById("promoImg");
            var partyButton = document.getElementById("partyButton");
            var joinPartyButton = document.getElementById("joinPartyButton");
            var settingsButton = document.getElementById("settingsButton");
            var settingsButtonTitle = settingsButton.getElementsByTagName("span")[0];
            var allianceButton = document.getElementById("allianceButton");
            var storeButton = document.getElementById("storeButton");
            var chatButton = document.getElementById("chatButton");
            var gameCanvas = document.getElementById("gameCanvas");
            var mainContext = gameCanvas.getContext("2d");
            var serverBrowser = document.getElementById("serverBrowser");
            var nativeResolutionCheckbox = document.getElementById("nativeResolution");
            var showPingCheckbox = document.getElementById("showPing");
            var playMusicCheckbox = document.getElementById("playMusic");
            var pingDisplay = document.getElementById("pingDisplay");
            var shutdownDisplay = document.getElementById("shutdownDisplay");
            var menuCardHolder = document.getElementById("menuCardHolder");
            var guideCard = document.getElementById("guideCard");
            var loadingText = document.getElementById("loadingText");
            var gameUI = document.getElementById("gameUI");
            var actionBar = document.getElementById("actionBar");
            var scoreDisplay = document.getElementById("scoreDisplay");
            var foodDisplay = document.getElementById("foodDisplay");
            var woodDisplay = document.getElementById("woodDisplay");
            var stoneDisplay = document.getElementById("stoneDisplay");
            var killCounter = document.getElementById("killCounter");
            var leaderboardData = document.getElementById("leaderboardData");
            var nameInput = document.getElementById("nameInput");
            var itemInfoHolder = document.getElementById("itemInfoHolder");
            var ageText = document.getElementById("ageText");
            var ageBarBody = document.getElementById("ageBarBody");
            var upgradeHolder = document.getElementById("upgradeHolder");
            var upgradeCounter = document.getElementById("upgradeCounter");
            var allianceMenu = document.getElementById("allianceMenu");
            var allianceHolder = document.getElementById("allianceHolder");
            var allianceManager = document.getElementById("allianceManager");
            var mapDisplay = document.getElementById("mapDisplay");
            var diedText = document.getElementById("diedText");
            var skinColorHolder = document.getElementById("skinColorHolder");
            var mapContext = mapDisplay.getContext("2d");
            mapDisplay.width = 300;
            mapDisplay.height = 300;
            var storeMenu = document.getElementById("storeMenu");
            var storeHolder = document.getElementById("storeHolder");
            var noticationDisplay = document.getElementById("noticationDisplay");
            var hats = store.hats;
            var accessories = store.accessories;
            var objectManager = new ObjectManager(GameObject, gameObjects, UTILS, config);
            var outlineColor = "#525252";
            var darkOutlineColor = "#3d3f42";
            var outlineWidth = 5.5;

            // SET INIT DATA:
            function setInitData(data) {
                alliances = data.teams;
            }

            // YOUTUBERS:
            var featuredYoutuber = document.getElementById('featuredYoutube');
            var youtuberList = [{
                name: "Corrupt X",
                link: "https://www.youtube.com/channel/UC0UH2LfQvBSeH24bmtbmITw"
            }, {
                name: "Tweak Big",
                link: "https://www.youtube.com/channel/UCbwvzJ38AndDTkoX8sD9YOw"
            }, {
                name: "Arena Closer",
                link: "https://www.youtube.com/channel/UCazucVSJqW-kiHMIhQhD-QQ"
            }, {
                name: "Godenot",
                link: "https://www.youtube.com/user/SirGodenot"
            }, {
                name: "RajNoobTV",
                link: "https://www.youtube.com/channel/UCVLo9brXBWrCttMaGzvm0-Q"
            }, {
                name: "TomNotTom",
                link: "https://www.youtube.com/channel/UC7z97RgHFJRcv2niXgArBDw"
            }, {
                name: "Nation",
                link: "https://www.youtube.com/channel/UCSl-MBn3qzjrIvLNESQRk-g"
            }, {
                name: "Pidyohago",
                link: "https://www.youtube.com/channel/UC04p8Mg8nDaDx04A9is2B8Q"
            }, {
                name: "Enigma",
                link: "https://www.youtube.com/channel/UC5HhLbs3sReHo8Bb9NDdFrg"
            }, {
                name: "Bauer",
                link: "https://www.youtube.com/channel/UCwU2TbJx3xTSlPqg-Ix3R1g"
            }, {
                name: "iStealth",
                link: "https://www.youtube.com/channel/UCGrvlEOsQFViZbyFDE6t69A"
            }, {
                name: "SICKmania",
                link: "https://www.youtube.com/channel/UCvVI98ezn4TpX5wDMZjMa3g"
            }, {
                name: "LightThief",
                link: "https://www.youtube.com/channel/UCj6C_tiDeATiKd3GX127XoQ"
            }, {
                name: "Fortish",
                link: "https://www.youtube.com/channel/UCou6CLU-szZA3Tb340TB9_Q"
            }, {
                name: "巧克力",
                link: "https://www.youtube.com/channel/UCgL6J6oL8F69vm-GcPScmwg"
            }, {
                name: "i Febag",
                link: "https://www.youtube.com/channel/UCiU6WZwiKbsnt5xmwr0OFbg"
            }, {
                name: "GoneGaming",
                link: "https://www.youtube.com/channel/UCOcQthRanYcwYY0XVyVeK0g"
            }];
            var tmpYoutuber = youtuberList[UTILS.randInt(0, youtuberList.length - 1)];
            featuredYoutuber = document.createElement("div");
            featuredYoutuber.innerHTML = "<a target='_blank' class='ytLink' href='" + tmpYoutuber.link + "'><i class='material-icons' style='vertical-align: top;'>&#xE064;</i> " + tmpYoutuber.name + "</a>";

            // ON LOAD:
            var inWindow = true;
            var didLoad = false;
            var captchaReady = false;
            window.onblur = function() {
                inWindow = false;
            };
            window.onfocus = function() {
                inWindow = true;
                if (player && player.alive) {
                    resetMoveDir();
                }
            };
            window.onload = function() {
                didLoad = true;
                connectSocketIfReady();

                setTimeout(function() {
                    if (!startedConnecting) {
                        alert("Captcha failed to load");
                        window.location.reload();
                    }
                }, 20 * 1000);
            };
            window.captchaCallback = function() {
                captchaReady = true;
                connectSocketIfReady();
            };
            gameCanvas.oncontextmenu = function() {
                return false;
            };
            function disconnect(reason) {
                connected = false;
                io.close();
                showLoadingText(reason);
            }
            function showLoadingText(text) {
                mainMenu.style.display = "block";
                gameUI.style.display = "none";
                menuCardHolder.style.display = "none";
                diedText.style.display = "none";
                loadingText.style.display = "block";
                loadingText.innerHTML = text +
                    "<a href='javascript:window.location.href=window.location.href' class='ytLink'>reload</a>";
            }

            // BUTTON EVENTS:
            function bindEvents() {
                enterGameButton.onclick = UTILS.checkTrusted(enterGame);
                UTILS.hookTouchEvents(enterGameButton);
                promoImageButton.onclick = UTILS.checkTrusted(function() {
                    openLink('https://krunker.io/?play=SquidGame_KB');
                });
                UTILS.hookTouchEvents(promoImageButton);
                joinPartyButton.onclick = UTILS.checkTrusted(function() {
                    setTimeout(function() { joinParty(); }, 10);
                });
                UTILS.hookTouchEvents(joinPartyButton);
                settingsButton.onclick = UTILS.checkTrusted(function() {
                    toggleSettings();
                });
                UTILS.hookTouchEvents(settingsButton);
                allianceButton.onclick = UTILS.checkTrusted(function() {
                    toggleAllianceMenu();
                });
                UTILS.hookTouchEvents(allianceButton);
                storeButton.onclick = UTILS.checkTrusted(function() {
                    toggleStoreMenu();
                });
                UTILS.hookTouchEvents(storeButton);
                chatButton.onclick = UTILS.checkTrusted(function() {
                    toggleChat();
                });
                UTILS.hookTouchEvents(chatButton);
                mapDisplay.onclick = UTILS.checkTrusted(function() {
                    sendMapPing();
                });
                UTILS.hookTouchEvents(mapDisplay);
            }

            // SETUP SERVER SELECTOR:
            let gamesPerServer = 1,
                serverData, bestServer, usedServer;

            function setupServerStatus() {
                const { host } = window.location;
                const parent = host.split(".")[0];

                let url = "";

                switch(parent) {
                    case "sandbox":
                        url = "https://api-sandbox.moomoo.io"
                        break;
                    case "dev":
                        url = "https://api-dev.moomoo.io";
                        break;
                    default:
                        url = "https://api.moomoo.io"
                        break;
                }

                fetch(`${url}/servers?v=1.22`).then((res) => res.json()).then((parsed) => {
                    serverData = parsed;

                    for(let server of serverData) {
                        const start = Date.now();

                        fetch(`https://${server.key}.${server.region}.moomoo.io/ping`).then(res => res.text()).then(res => {
                            server.ping = Date.now() - start;
                        }).catch((event) => {});
                    }

                    setTimeout(() => updateServerList(true), 2e3);
                })
            }

            const fastServer = (servers) => {
                const availableServers = servers.filter(server => server.playerCount !== server.playerCapacity);

                if(!availableServers.length) return null;

                const bestPing = Math.min(...serverData.map(server => server.ping || Infinity));

                const bestPingServers = availableServers.filter(server => (!usedServer || (server.key !== usedServer)) && server.ping === bestPing);

                if(!bestPingServers.length) return null;

                const result = bestPingServers.reduce((bestServer, currentServer) => {
                    return bestServer.playerCount > currentServer.playerCount ? bestServer : currentServer
                });

                return result;
            }

            const regionInfo = {
                0: {
                    name: "Local",
                    latitude: 0,
                    longitude: 0,
                },
                "us-east": {
                    name: "Miami",
                    latitude: 40.1393329,
                    longitude: -75.8521818,
                },
                "us-west": {
                    name: "Silicon Valley",
                    latitude: 47.6149942,
                    longitude: -122.4759879,
                },
                gb: {
                    name: "London",
                    latitude: 51.5283063,
                    longitude: -0.382486,
                },
                "eu-west": {
                    name: "Frankfurt",
                    latitude: 50.1211273,
                    longitude: 8.496137,
                },
                au: {
                    name: "Sydney",
                    latitude: -33.8479715,
                    longitude: 150.651084,
                },
                sg: {
                    name: "Singapore",
                    latitude: 1.3147268,
                    longitude: 103.7065876,
                },
            };

            function stripRegionPrefix(region) {
                if (region.startsWith("vultr:")) {
                    return region.slice(6);
                } else if (region.startsWith("do:")) {
                    return region.slice(3);
                } else {
                    return region;
                }
            }

            let listOpen;

            function updateServerList(html) {
                if(!html) return setupServerStatus();

                if(!startedConnecting && !connected) bestServer = fastServer(serverData);

                if(listOpen) return;
                var tmpHTML = `<select id="serverSelector">`;

                // ADD SERVER SELECTOR:
                var overallTotal = 0;
                var regionCounter = 0;

                let lastRegion;

                for (var index in serverData) {
                    var server = serverData[index];

                    // ADD REGION LABELS:
                    var regionName = regionInfo[server.region].name;

                    // COUNT PLAYERS:
                    if(regionName !== lastRegion) {
                        var totalPlayers = 0;

                        serverData.forEach(server_ => {
                            if(server_.region === server.region) totalPlayers += server_.playerCount;
                        });

                        overallTotal += totalPlayers;

                        if(lastRegion) tmpHTML += `<option disabled></option>`;

                        tmpHTML += "<option disabled>" + regionName + " - " + totalPlayers + " players</option>"
                    }

                    let isSelected = bestServer && bestServer.region === server.region && bestServer.key === server.key ? "selected" : "";
                    let serverID = stripRegionPrefix(server.region) + ":" + server.key;
                    let serverLabel = `${regionName} ${server.name} [${server.playerCount}/${server.playerCapacity}] ${(server.ping || Infinity)}ms`;

                    if (isSelected) partyButton.getElementsByTagName("span")[0].innerText = server.name;

                    tmpHTML += `<option value=${serverID} ${isSelected}>${serverLabel}</option>`;

                    // ADD BREAK AFTER EACH SERVER:
                    if(lastRegion !== regionName) {
                        lastRegion = regionName;
                        // tmpHTML += "<option disabled></option>";

                        // INCREMENT COUNTER:
                        regionCounter++;
                    }
                }

                // ADD TOTAL PLAYERS:
                tmpHTML += "<option disabled></option><option disabled>All Servers - " + overallTotal + " players</option></select>";

                // SET HTML:
                serverBrowser.innerHTML = tmpHTML;

                // ALT SERVER:
                var altServerText;
                var altServerURL;
                if (location.hostname == "sandbox.moomoo.io") {
                    altServerText = "Back to MooMoo";
                    altServerURL = "//moomoo.io/";
                } else {
                    altServerText = "Try the sandbox";
                    altServerURL = "//sandbox.moomoo.io/";
                }

                document.getElementById("altServer").innerHTML = "<a href='" + altServerURL + "'>" + altServerText + "<i class='material-icons' style='font-size:10px;vertical-align:middle'>arrow_forward_ios</i></a>";

                let selector = document.getElementById("serverSelector");

                const events = [{
                    name: "change",
                    start: function(event){
                        let part = this.value.split(":");

                        this.blur();

                        io.close();
                        disconnect("disconnected");

                        setTimeout(() => {
                            showLoadingText("Loading...");

                            inGame = false;
                            player = null;
                            playerSID = null;
                            players = [];
                            gameObjects = [];
                            alliances = [];
                            GameObject = __webpack_require__(/*! ./data/gameObject.js */ "./src/js/data/gameObject.js");
                            MapManager = __webpack_require__(/*! ./data/mapManager.js */ "./src/js/data/mapManager.js");
                            objectManager = new ObjectManager(GameObject, gameObjects, UTILS, config);
                            Player = __webpack_require__(/*! ./data/player.js */ "./src/js/data/player.js");
                            store = __webpack_require__(/*! ./data/store.js */ "./src/js/data/store.js");
                            Projectile = __webpack_require__(/*! ./data/projectile.js */ "./src/js/data/projectile.js");
                            SoundManager = __webpack_require__(/*! ./libs/soundManager.js */ "./src/js/libs/soundManager.js").obj;
                            textManager = new animText.TextManager();
                            prepareMenuBackground();

                            let server = serverData.find(server => server.region === part[0] && server.key === part[1]);

                            let region = regionInfo[server.region].name.toLowerCase(),
                                name = server.name;

                            connectSocketIfReady(server);
                        }, 2e3);
                    }
                },{
                    name: "blur",
                    start: function(event){
                        listOpen = false;
                    }
                },{
                    name: "focus",
                    start: function(event){
                        listOpen = true;
                    }
                },{
                    name: "keyup",
                    start: function(event){
                        listOpen = true;
                    }
                }];

                for(let event of events) {
                    document.getElementById("serverSelector").addEventListener(event.name, function(){
                        event.start.call(this, event);
                    })
                }
            }

            updateServerList();
            setInterval(updateServerList, 3e3);
            // SERVER SELECTOR CHANGE LISTENER:
            serverBrowser.addEventListener("change", UTILS.checkTrusted(function() {
                //  let parts = serverBrowser.value.split(":");
                //   console.log(serverBrowser.value)
                //  vultrClient.switchServer(parts[0], parts[1], parts[2]);
            }));

            // SHOW ITEM INFO:
            function showItemInfo(item, isWeapon, isStoreItem) {
                if (player && item) {
                    UTILS.removeAllChildren(itemInfoHolder);
                    itemInfoHolder.classList.add("visible");
                    // chatButton.classList.add("hide");
                    UTILS.generateElement({
                        id: "itemInfoName",
                        text: UTILS.capitalizeFirst(item.name),
                        parent: itemInfoHolder
                    });
                    UTILS.generateElement({
                        id: "itemInfoDesc",
                        text: item.desc,
                        parent: itemInfoHolder
                    });
                    if (isStoreItem) {

                    } else if (isWeapon) {
                        UTILS.generateElement({
                            class: "itemInfoReq",
                            text: !item.type?"primary":"secondary",
                            parent: itemInfoHolder
                        });
                    } else {
                        for (var i = 0; i < item.req.length; i += 2) {
                            UTILS.generateElement({
                                class: "itemInfoReq",
                                html: item.req[i] + "<span class='itemInfoReqVal'> x" + item.req[i + 1] + "</span>",
                                parent: itemInfoHolder
                            });
                        }
                        if (item.group.limit) {
                            UTILS.generateElement({
                                class: "itemInfoLmt",
                                text: (player.itemCounts[item.group.id]||0) + "/" + item.group.limit,
                                parent: itemInfoHolder
                            });
                        }
                    }
                } else {
                    itemInfoHolder.classList.remove("visible");
                    // chatButton.classList.remove("hide");
                }
            }

            // SHOW ALLIANCE MENU:
            var allianceNotifications = [];
            var alliancePlayers = [];

            function ally(sid){
                if(sid === player.sid) return true;

                if(!alliancePlayers.length) return false;

                for(let index = 0; index < alliancePlayers.length; index += 2){
                    const _sid = alliancePlayers[index];

                    if(sid == _sid) return true;
                }
                return false;
            }

            function allianceNotification(sid, name) {
                allianceNotifications.push({
                    sid: sid,
                    name: name
                });
                updateNotifications();
            }
            function updateNotifications() {
                if (allianceNotifications[0]) {
                    var tmpN = allianceNotifications[0];
                    UTILS.removeAllChildren(noticationDisplay);
                    noticationDisplay.style.display = "block";
                    UTILS.generateElement({
                        class: "notificationText",
                        text: tmpN.name,
                        parent: noticationDisplay
                    });
                    UTILS.generateElement({
                        class: "notifButton",
                        html: "<i class='material-icons' style='font-size:28px;color:#cc5151;'>&#xE14C;</i>",
                        parent: noticationDisplay,
                        onclick: function() { aJoinReq(0); },
                        hookTouch: true
                    });
                    UTILS.generateElement({
                        class: "notifButton",
                        html: "<i class='material-icons' style='font-size:28px;color:#8ecc51;'>&#xE876;</i>",
                        parent: noticationDisplay,
                        onclick: function() { aJoinReq(1); },
                        hookTouch: true
                    });
                } else {
                    noticationDisplay.style.display = "none";
                }
            }
            function addAlliance(data) {
                alliances.push(data);
                if (allianceMenu.style.display == "block")
                    showAllianceMenu();
            }
            function setPlayerTeam(team, isOwner) {
                if (player) {
                    player.team = team;
                    player.isOwner = isOwner;
                    if (allianceMenu.style.display == "block")
                        showAllianceMenu();
                }
            }
            function setAlliancePlayers(data) {
                alliancePlayers = data;
                if (allianceMenu.style.display == "block")
                    showAllianceMenu();
            }
            function deleteAlliance(sid) {
                for (var i = alliances.length - 1; i >= 0; i--) {
                    if (alliances[i].sid == sid)
                        alliances.splice(i, 1);
                }
                if (allianceMenu.style.display == "block")
                    showAllianceMenu();
            }
            function toggleAllianceMenu() {
                resetMoveDir();
                if (allianceMenu.style.display != "block") {
                    showAllianceMenu();
                } else {
                    allianceMenu.style.display = "none";
                }
            }
            function showAllianceMenu() {
                if (player && player.alive) {
                    closeChat();
                    storeMenu.style.display = "none";
                    allianceMenu.style.display = "block";
                    UTILS.removeAllChildren(allianceHolder);
                    if (player.team) {
                        for (var i = 0; i < alliancePlayers.length; i+=2) {
                            (function(i) {
                                var tmp = UTILS.generateElement({
                                    class: "allianceItem",
                                    style: "color:" + (alliancePlayers[i]==player.sid ? "#fff" : "rgba(255,255,255,0.6)"),
                                    text: alliancePlayers[i+1],
                                    parent: allianceHolder
                                });
                                if (player.isOwner && alliancePlayers[i] != player.sid) {
                                    UTILS.generateElement({
                                        class: "joinAlBtn",
                                        text: "Kick",
                                        onclick: function() { kickFromClan(alliancePlayers[i]); },
                                        hookTouch: true,
                                        parent: tmp
                                    });
                                }
                            })(i);
                        }
                    } else {
                        if (alliances.length) {
                            for (var i = 0; i < alliances.length; ++i) {
                                (function(i) {
                                    var tmp = UTILS.generateElement({
                                        class: "allianceItem",
                                        style: "color:" + (alliances[i].sid==player.team ? "#fff" : "rgba(255,255,255,0.6)"),
                                        text: alliances[i].sid,
                                        parent: allianceHolder
                                    });
                                    UTILS.generateElement({
                                        class: "joinAlBtn",
                                        text: "Join",
                                        onclick: function() { sendJoin(i); },
                                        hookTouch: true,
                                        parent: tmp
                                    });
                                })(i);
                            }
                        } else {
                            UTILS.generateElement({
                                class: "allianceItem",
                                text: "No Tribes Yet",
                                parent: allianceHolder
                            });
                        }
                    }
                    UTILS.removeAllChildren(allianceManager);
                    if (player.team) {
                        UTILS.generateElement({
                            class: "allianceButtonM",
                            style: "width: 360px",
                            text: player.isOwner? "Delete Tribe" : "Leave Tribe",
                            onclick: function() { leaveAlliance() },
                            hookTouch: true,
                            parent: allianceManager
                        });
                    } else {
                        UTILS.generateElement({
                            tag: "input",
                            type: "text",
                            id: "allianceInput",
                            maxLength: 7,
                            placeholder: "unique name",
                            ontouchstart: function(ev) {
                                ev.preventDefault();
                                var newValue = prompt("unique name", ev.currentTarget.value);
                                ev.currentTarget.value = newValue.slice(0, 7);
                            },
                            parent: allianceManager
                        });
                        UTILS.generateElement({
                            tag: "div",
                            class: "allianceButtonM",
                            style: "width: 140px;",
                            text: "Create",
                            onclick: function() { createAlliance(); },
                            hookTouch: true,
                            parent: allianceManager
                        });
                    }
                }
            }
            function aJoinReq(join) {
                io.send("11", allianceNotifications[0].sid, join);
                allianceNotifications.splice(0, 1);
                updateNotifications();
            }
            function kickFromClan(sid) {
                io.send("12", sid);
            }
            function sendJoin(index) {
                io.send("10", alliances[index].sid);
            }
            function createAlliance() {
                io.send("8", document.getElementById("allianceInput").value);
            }
            function leaveAlliance() {
                allianceNotifications = [];
                updateNotifications();
                io.send("9");
            }

            // window.testRateLimiting = function() {
            //     setInterval(() => {
            //         if (Math.random() > 0.5) {
            //             io.send("8", "test");
            //         } else {
            //             io.send("9");
            //         }
            //     }, 50);
            // }

            // MINIMAP:
            var lastDeath;
            var minimapData;
            var mapMarker;
            var mapPings = [];
            var tmpPing;
            function MapPing() {
                this.init = function(x, y) {
                    this.scale = 0;
                    this.x = x;
                    this.y = y;
                    this.active = true;
                };
                this.update = function(ctxt, delta) {
                    if (this.active) {
                        this.scale += 0.05 * delta;
                        if (this.scale >= config.mapPingScale) {
                            this.active = false;
                        } else {
                            ctxt.globalAlpha = (1-Math.max(0, this.scale/config.mapPingScale));
                            ctxt.beginPath();
                            ctxt.arc((this.x / config.mapScale) * mapDisplay.width, (this.y / config.mapScale)
                                     * mapDisplay.width, this.scale, 0, 2 * Math.PI);
                            ctxt.stroke();
                        }
                    }
                };
            }
            function pingMap(x, y) {
                for (var i = 0; i < mapPings.length; ++i) {
                    if (!mapPings[i].active) {
                        tmpPing = mapPings[i];
                        break;
                    }
                }
                if (!tmpPing) {
                    tmpPing = new MapPing();
                    mapPings.push(tmpPing);
                }
                tmpPing.init(x, y);
            }
            function updateMapMarker() {
                if (!mapMarker)
                    mapMarker = {};
                mapMarker.x = player.x;
                mapMarker.y = player.y;
            }
            function updateMinimap(data) {
                minimapData = data;
            }
            function renderMinimap(delta) {
                if (player && player.alive) {
                    mapContext.clearRect(0, 0, mapDisplay.width, mapDisplay.height);

                    // RENDER PINGS:
                    mapContext.strokeStyle = "#fff";
                    mapContext.lineWidth = 4;
                    for (var i = 0; i < mapPings.length; ++i) {
                        tmpPing = mapPings[i];
                        tmpPing.update(mapContext, delta);
                    }

                    // RENDER PLAYERS:
                    mapContext.globalAlpha = 1;
                    mapContext.fillStyle = "#fff";
                    renderCircle((player.x/config.mapScale)*mapDisplay.width,
                                 (player.y/config.mapScale)*mapDisplay.height, 7, mapContext, true);
                    mapContext.fillStyle = "rgba(255,255,255,0.35)";
                    if (player.team && minimapData) {
                        for (var i = 0; i < minimapData.length;) {
                            renderCircle((minimapData[i]/config.mapScale)*mapDisplay.width,
                                         (minimapData[i+1]/config.mapScale)*mapDisplay.height, 7, mapContext, true);
                            i+=2;
                        }
                    }

                    // DEATH LOCATION:
                    if (lastDeath) {
                        mapContext.fillStyle = "#fc5553";
                        mapContext.font = "34px Hammersmith One";
                        mapContext.textBaseline = "middle";
                        mapContext.textAlign = "center";
                        mapContext.fillText("x", (lastDeath.x/config.mapScale)*mapDisplay.width,
                                            (lastDeath.y/config.mapScale)*mapDisplay.height);
                    }

                    // MAP MARKER:
                    if (mapMarker) {
                        mapContext.fillStyle = "#fff";
                        mapContext.font = "34px Hammersmith One";
                        mapContext.textBaseline = "middle";
                        mapContext.textAlign = "center";
                        mapContext.fillText("x", (mapMarker.x/config.mapScale)*mapDisplay.width,
                                            (mapMarker.y/config.mapScale)*mapDisplay.height);
                    }
                }
            }

            // STORE MENU:
            var currentStoreIndex = 0;
            var playerItems = {};
            function changeStoreIndex(index) {
                if (currentStoreIndex != index) {
                    currentStoreIndex = index;
                    generateStoreList();
                }
            }
            function toggleStoreMenu() {
                if (storeMenu.style.display != "block") {
                    storeMenu.style.display = "block";
                    allianceMenu.style.display = "none";
                    closeChat();
                    generateStoreList();
                } else {
                    storeMenu.style.display = "none";
                }
            }
            function updateStoreItems(type, id, index) {
                if (index) {
                    if (!type)
                        player.tails[id] = 1;
                    else
                        player.tailIndex = id;
                } else {
                    if (!type)
                        player.skins[id] = 1;
                    else
                        player.skinIndex = id;
                }
                if (storeMenu.style.display == "block")
                    generateStoreList();
            }
            function generateStoreList() {
                if (player) {
                    UTILS.removeAllChildren(storeHolder);
                    var index = currentStoreIndex;
                    var tmpArray = index?accessories:hats;
                    for (var i = 0; i < tmpArray.length; ++i) {
                        if (!tmpArray[i].dontSell) {
                            (function(i) {
                                var tmp = UTILS.generateElement({
                                    id: "storeDisplay" + i,
                                    class: "storeItem",
                                    onmouseout: function() { showItemInfo(); },
                                    onmouseover: function() { showItemInfo(tmpArray[i], false, true); },
                                    parent: storeHolder
                                });
                                UTILS.hookTouchEvents(tmp, true);
                                UTILS.generateElement({
                                    tag: "img",
                                    class: "hatPreview",
                                    src: "../img/" + (index?"accessories/access_":"hats/hat_") + tmpArray[i].id + (tmpArray[i].topSprite?"_p":"") + ".png",
                                    parent: tmp
                                });
                                UTILS.generateElement({
                                    tag: "span",
                                    text: tmpArray[i].name,
                                    parent: tmp
                                });
                                if (index?(!player.tails[tmpArray[i].id]):(!player.skins[tmpArray[i].id])) {
                                    UTILS.generateElement({
                                        class: "joinAlBtn",
                                        style: "margin-top: 5px",
                                        text: "Buy",
                                        onclick: function() { storeBuy(tmpArray[i].id, index); },
                                        hookTouch: true,
                                        parent: tmp
                                    });
                                    UTILS.generateElement({
                                        tag: "span",
                                        class: "itemPrice",
                                        text: tmpArray[i].price,
                                        parent: tmp
                                    })
                                } else if ((index?player.tailIndex:player.skinIndex)==tmpArray[i].id) {
                                    UTILS.generateElement({
                                        class: "joinAlBtn",
                                        style: "margin-top: 5px",
                                        text: "Unequip",
                                        onclick: function() { storeEquip(0, index); },
                                        hookTouch: true,
                                        parent: tmp
                                    });
                                } else {
                                    UTILS.generateElement({
                                        class: "joinAlBtn",
                                        style: "margin-top: 5px",
                                        text: "Equip",
                                        onclick: function() { storeEquip(tmpArray[i].id, index); },
                                        hookTouch: true,
                                        parent: tmp
                                    });
                                }
                            })(i);
                        }
                    }
                }
            }
            function storeEquip(id, index) {
                io.send("13c", 0, id, index);
            }
            function storeBuy(id, index) {
                io.send("13c", 1, id, index);
            }

            // HIDE WINDOWS:
            function hideAllWindows() {
                storeMenu.style.display = "none";
                allianceMenu.style.display = "none";
                closeChat();
            }

            // PREPARE UI:
            function prepareUI() {

                // NATIVE RESOLUTION:
                var savedNativeValue = getSavedVal("native_resolution");
                if (!savedNativeValue) {
                    setUseNativeResolution(typeof cordova !== "undefined"); // Only default to native if on mobile
                } else {
                    setUseNativeResolution(savedNativeValue == "true");
                }

                // SHOW PING:
                showPing = getSavedVal("show_ping") == "true";
                pingDisplay.hidden = !showPing;

                // LOAD SOUND SETTING:
                playSound = getSavedVal("moo_moosic")||0;

                // SKIN COLOR PICKER:
                updateSkinColorPicker();

                // ACTION BAR:
                UTILS.removeAllChildren(actionBar);
                for (var i = 0; i < (items.weapons.length+items.list.length); ++i) {
                    (function(i) {
                        UTILS.generateElement({
                            id: "actionBarItem" + i,
                            class: "actionBarItem",
                            style: "display:none",
                            onmouseout: function() {
                                showItemInfo();
                            },
                            parent: actionBar
                        });
                    })(i);
                }
                for (var i = 0; i < (items.list.length + items.weapons.length); ++i) {
                    (function(i) {
                        var tmpCanvas = document.createElement('canvas');
                        tmpCanvas.width = tmpCanvas.height = 66;
                        var tmpContext = tmpCanvas.getContext('2d');
                        tmpContext.translate((tmpCanvas.width / 2), (tmpCanvas.height / 2));
                        tmpContext.imageSmoothingEnabled = false;
                        tmpContext.webkitImageSmoothingEnabled = false;
                        tmpContext.mozImageSmoothingEnabled = false;
                        if (items.weapons[i]) {
                            tmpContext.rotate((Math.PI/4)+Math.PI);
                            var tmpSprite = new Image();
                            toolSprites[items.weapons[i].src] = tmpSprite;
                            tmpSprite.onload = function() {
                                this.isLoaded = true;
                                var tmpPad = 1 / (this.height / this.width);
                                var tmpMlt = (items.weapons[i].iPad || 1);
                                tmpContext.drawImage(this, -(tmpCanvas.width*tmpMlt*config.iconPad*tmpPad)/2, -(tmpCanvas.height*tmpMlt*config.iconPad)/2,
                                                     tmpCanvas.width*tmpMlt*tmpPad*config.iconPad, tmpCanvas.height*tmpMlt*config.iconPad);
                                tmpContext.fillStyle = "rgba(0, 0, 70, 0.1)";
                                tmpContext.globalCompositeOperation = "source-atop";
                                tmpContext.fillRect(-tmpCanvas.width / 2, -tmpCanvas.height / 2, tmpCanvas.width, tmpCanvas.height);
                                document.getElementById('actionBarItem' + i).style.backgroundImage = "url(" + tmpCanvas.toDataURL() + ")";
                            };
                            tmpSprite.src = ".././img/weapons/" + items.weapons[i].src + ".png";
                            var tmpUnit = document.getElementById('actionBarItem' + i);
                            tmpUnit.onmouseover = UTILS.checkTrusted(function() {
                                showItemInfo(items.weapons[i], true);
                            });
                            tmpUnit.onclick = UTILS.checkTrusted(function() {
                                selectToBuild(i, true);
                            });
                            UTILS.hookTouchEvents(tmpUnit);
                        } else {
                            var tmpSprite = getItemSprite(items.list[i-items.weapons.length], true);
                            var tmpScale = Math.min(tmpCanvas.width - config.iconPadding, tmpSprite.width);
                            tmpContext.globalAlpha = 1;
                            tmpContext.drawImage(tmpSprite, -tmpScale / 2, -tmpScale / 2, tmpScale, tmpScale);
                            tmpContext.fillStyle = "rgba(0, 0, 70, 0.1)";
                            tmpContext.globalCompositeOperation = "source-atop";
                            tmpContext.fillRect(-tmpScale / 2, -tmpScale / 2, tmpScale, tmpScale);
                            document.getElementById('actionBarItem' + i).style.backgroundImage = "url(" + tmpCanvas.toDataURL() + ")";
                            var tmpUnit = document.getElementById('actionBarItem' + i);
                            tmpUnit.onmouseover = UTILS.checkTrusted(function() {
                                showItemInfo(items.list[i-items.weapons.length]);
                            });
                            tmpUnit.onclick = UTILS.checkTrusted(function() {
                                selectToBuild(i-items.weapons.length);
                            });
                            UTILS.hookTouchEvents(tmpUnit);
                        }
                    })(i);
                }

                // MOBILE NAME INPUT:
                nameInput.ontouchstart = UTILS.checkTrusted(function(e) {
                    e.preventDefault();
                    var newValue = prompt("enter name", e.currentTarget.value);
                    e.currentTarget.value = newValue.slice(0, 15);
                });

                // SETTINGS:
                nativeResolutionCheckbox.checked = useNativeResolution;
                nativeResolutionCheckbox.onchange = UTILS.checkTrusted(function(e) {
                    setUseNativeResolution(e.target.checked);
                });
                showPingCheckbox.checked = showPing;
                showPingCheckbox.onchange = UTILS.checkTrusted(function(e) {
                    showPing = showPingCheckbox.checked;
                    pingDisplay.hidden = !showPing;
                    saveVal("show_ping", showPing ? "true" : "false");
                });

                // PLAY MENU SOUND:
                // Sound.play("menu", 1, true);
            }
            function updateItems(data, wpn) {
                if (data) {
                    if (wpn) player.weapons = data;
                    else player.items = data;
                }
                for (var i = 0; i < items.list.length; ++i) {
                    var tmpI = (items.weapons.length + i);
                    document.getElementById("actionBarItem" + tmpI).style.display = (player.items.indexOf(items.list[i].id)>=0)?"inline-block":"none";
                }
                for (var i = 0; i < items.weapons.length; ++i) {
                    document.getElementById("actionBarItem" + i).style.display =
                        (player.weapons[items.weapons[i].type]==items.weapons[i].id)?"inline-block":"none";
                }
            }
            function setUseNativeResolution(useNative) {
                useNativeResolution = useNative;
                pixelDensity = useNative ? (window.devicePixelRatio || 1) : 1;
                nativeResolutionCheckbox.checked = useNative;
                saveVal("native_resolution", useNative.toString());
                resize();
            }
            function updateGuide() {
                if (usingTouch) {
                    guideCard.classList.add("touch");
                } else {
                    guideCard.classList.remove("touch");
                }
            }

            // SETTINGS STUFF:
            function toggleSettings() {
                if (guideCard.classList.contains("showing")) {
                    guideCard.classList.remove("showing");
                    settingsButtonTitle.innerText = "Settings";
                } else {
                    guideCard.classList.add("showing");
                    settingsButtonTitle.innerText = "Close";
                }
            }

            // SELECT SKIN COLOR:
            function updateSkinColorPicker() {
                var tmpHTML = "";
                for (var i = 0; i < config.skinColors.length; ++i) {
                    if (i == skinColor) {
                        tmpHTML += ("<div class='skinColorItem activeSkin' style='background-color:" +
                                    config.skinColors[i] + "' onclick='selectSkinColor(" + i + ")'></div>");
                    } else {
                        tmpHTML += ("<div class='skinColorItem' style='background-color:" +
                                    config.skinColors[i] + "' onclick='selectSkinColor(" + i + ")'></div>");
                    }
                }
                skinColorHolder.innerHTML = tmpHTML;
            }
            function selectSkinColor(index) {
                skinColor = index;
                updateSkinColorPicker();
            }

            // CHAT STUFF:
            var chatBox = document.getElementById("chatBox");
            var chatHolder = document.getElementById("chatHolder");
            function toggleChat() {
                if (!usingTouch) {
                    if (chatHolder.style.display == "block") {
                        if (chatBox.value) {
                            sendChat(chatBox.value);
                        }
                        closeChat();
                    } else {
                        storeMenu.style.display = "none";
                        allianceMenu.style.display = "none";
                        chatHolder.style.display = "block";
                        chatBox.focus();
                        resetMoveDir();
                    }
                } else {
                    setTimeout(function() { // Timeout lets the `hookTouchEvents` function exit
                        var chatMessage = prompt("chat message");
                        if (chatMessage) {
                            sendChat(chatMessage);
                        }
                    }, 1);
                }
                chatBox.value = "";
            }
            function sendChat(message) {
                io.send("ch", message.slice(0, 30));
            }
            function closeChat() {
                chatBox.value = "";
                chatHolder.style.display = "none";
            }

            // SEND MESSAGE:
            var profanityList = ["cunt", "whore", "fuck", "shit", "faggot", "nigger",
                                 "nigga", "dick", "vagina", "minge", "cock", "rape", "cum", "sex",
                                 "tits", "penis", "clit", "pussy", "meatcurtain", "jizz", "prune",
                                 "douche", "wanker", "damn", "bitch", "dick", "fag", "bastard"];
            function checkProfanityString(text) {
                var tmpString;
                for (var i = 0; i < profanityList.length; ++i) {
                    if (text.indexOf(profanityList[i]) > -1) {
                        tmpString = "";
                        for (var y = 0; y < profanityList[i].length; ++y) {
                            tmpString += tmpString.length?"o":"M";
                        }
                        var re = new RegExp(profanityList[i], 'g');
                        text = text.replace(re, tmpString);
                    }
                }
                return text;
            }
            function receiveChat(sid, message) {
                var tmpPlayer = findPlayerBySID(sid);
                if (tmpPlayer) {
                    tmpPlayer.chatMessage = checkProfanityString(message);
                    tmpPlayer.chatCountdown = config.chatCountdown;
                }
            }

            // RESIZE:
            window.addEventListener('resize', UTILS.checkTrusted(resize));
            function resize() {
                screenWidth = window.innerWidth;
                screenHeight = window.innerHeight;
                var scaleFillNative = Math.max(screenWidth / maxScreenWidth, screenHeight / maxScreenHeight) * pixelDensity;
                gameCanvas.width = screenWidth * pixelDensity;
                gameCanvas.height = screenHeight * pixelDensity;
                gameCanvas.style.width = screenWidth + "px";
                gameCanvas.style.height = screenHeight + "px";
                mainContext.setTransform(
                    scaleFillNative, 0,
                    0, scaleFillNative,
                    (screenWidth * pixelDensity - (maxScreenWidth * scaleFillNative)) / 2,
                    (screenHeight * pixelDensity - (maxScreenHeight * scaleFillNative)) / 2
                );
            }
            resize();

            // TOUCH INPUT:
            var usingTouch;
            setUsingTouch(false);
            function setUsingTouch(using) {
                usingTouch = using;
                updateGuide();
                // if (using) {
                //     chatButton.classList.add("mobile");
                // } else {
                //     chatButton.classList.remove("mobile");
                // }
            }
            window.setUsingTouch = setUsingTouch;

            gameCanvas.addEventListener('touchmove', UTILS.checkTrusted(touchMove), false);
            function touchMove(ev) {
                ev.preventDefault();
                ev.stopPropagation();
                setUsingTouch(true);
                for (var i = 0; i < ev.changedTouches.length; i++) {
                    var t = ev.changedTouches[i];
                    if (t.identifier == controllingTouch.id) {
                        controllingTouch.currentX = t.pageX;
                        controllingTouch.currentY = t.pageY;
                        sendMoveDir();
                    } else if (t.identifier == attackingTouch.id) {
                        attackingTouch.currentX = t.pageX;
                        attackingTouch.currentY = t.pageY;
                        attackState = 1;
                    }
                }
            }
            gameCanvas.addEventListener('touchstart', UTILS.checkTrusted(touchStart), false);
            function touchStart(ev) {
                ev.preventDefault();
                ev.stopPropagation();
                setUsingTouch(true);
                for (var i = 0; i < ev.changedTouches.length; i++) {
                    var t = ev.changedTouches[i];
                    if (t.pageX < document.body.scrollWidth / 2 && controllingTouch.id == -1) {
                        controllingTouch.id = t.identifier;
                        controllingTouch.startX = controllingTouch.currentX = t.pageX;
                        controllingTouch.startY = controllingTouch.currentY = t.pageY;
                        sendMoveDir();
                    } else if (t.pageX > document.body.scrollWidth / 2 && attackingTouch.id == -1) {
                        attackingTouch.id = t.identifier;
                        attackingTouch.startX = attackingTouch.currentX = t.pageX;
                        attackingTouch.startY = attackingTouch.currentY = t.pageY;
                        if (player.buildIndex < 0) {
                            attackState = 1;
                            sendAtckState();
                        }
                    }
                }
            }
            gameCanvas.addEventListener('touchend', UTILS.checkTrusted(touchEnd), false);
            gameCanvas.addEventListener('touchcancel', UTILS.checkTrusted(touchEnd), false);
            gameCanvas.addEventListener('touchleave', UTILS.checkTrusted(touchEnd), false);
            function touchEnd(ev) {
                ev.preventDefault();
                ev.stopPropagation();
                setUsingTouch(true);
                for (var i = 0; i < ev.changedTouches.length; i++) {
                    var t = ev.changedTouches[i];
                    if (t.identifier == controllingTouch.id) {
                        controllingTouch.id = -1;
                        sendMoveDir();
                    } else if (t.identifier == attackingTouch.id) {
                        attackingTouch.id = -1;
                        if (player.buildIndex >= 0) {
                            attackState = 1;
                            sendAtckState();
                        }
                        attackState = 0;
                        sendAtckState();
                    }
                }
            }


            // MOUSE INPUT:
            gameCanvas.addEventListener('mousemove', gameInput, false);
            function gameInput(e) {
                e.preventDefault();
                e.stopPropagation();
                setUsingTouch(false);
                mouseX = e.clientX;
                mouseY = e.clientY;
            }
            gameCanvas.addEventListener('mousedown', mouseDown, false);
            function mouseDown(e) {
                setUsingTouch(false);
                if (attackState != 1) {
                    attackState = 1;
                    sendAtckState();
                }
            }
            gameCanvas.addEventListener('mouseup', mouseUp, false);
            function mouseUp(e) {
                setUsingTouch(false);
                if (attackState != 0) {
                    attackState = 0;
                    sendAtckState();
                }
            }

            // INPUT UTILS:
            function getMoveDir() {
                var dx = 0;
                var dy = 0;
                if (controllingTouch.id != -1) {
                    dx += controllingTouch.currentX - controllingTouch.startX;
                    dy += controllingTouch.currentY - controllingTouch.startY;
                } else {
                    for (var key in moveKeys) {
                        var tmpDir = moveKeys[key];
                        dx += !!keys[key] * tmpDir[0];
                        dy += !!keys[key] * tmpDir[1];
                    }
                }
                return (dx == 0 && dy == 0) ? undefined : UTILS.fixTo(Math.atan2(dy, dx), 2);
            }
            var lastDir;
            function getAttackDir() {
                if (!player)
                    return 0;
                if (attackingTouch.id != -1) {
                    lastDir = Math.atan2(
                        attackingTouch.currentY - attackingTouch.startY,
                        attackingTouch.currentX - attackingTouch.startX
                    );
                } else if (!player.lockDir && !usingTouch) {
                    lastDir = Math.atan2(mouseY - (screenHeight / 2), mouseX - (screenWidth / 2));
                }
                return UTILS.fixTo(lastDir || 0, 2);
            }

            // KEYS:
            var keys = {};
            var moveKeys = {
                87: [0,-1],
                38: [0,-1],
                83: [0,1],
                40: [0,1],
                65: [-1,0],
                37: [-1,0],
                68: [1,0],
                39: [1,0]
            };
            function resetMoveDir() {
                keys = {};
                io.send("rmd");
            }
            function keysActive() {
                return (allianceMenu.style.display != "block"
                        && chatHolder.style.display != "block");
            }
            function keyDown(event) {
                var keyNum = event.which||event.keyCode||0;
                if (keyNum == 27) {
                    hideAllWindows();
                } else if (player && player.alive && keysActive()) {
                    if (!keys[keyNum]) {
                        keys[keyNum] = 1;
                        if (keyNum == 69) {
                            sendAutoGather();
                        } else if (keyNum == 67) {
                            updateMapMarker();
                        } else if (keyNum == 88) {
                            sendLockDir();
                        } else if (player.weapons[keyNum - 49] != undefined) {
                            selectToBuild(player.weapons[keyNum - 49], true);
                        } else if (player.items[keyNum - 49 - player.weapons.length] != undefined) {
                            selectToBuild(player.items[keyNum - 49 - player.weapons.length]);
                        } else if (keyNum == 81) {
                            selectToBuild(player.items[0]);
                        } else if (keyNum == 82) {
                            sendMapPing();
                        } else if (moveKeys[keyNum]) {
                            sendMoveDir();
                        } else if (keyNum == 32) {
                            attackState = 1;
                            sendAtckState();
                        }
                    }
                }
            }
            window.addEventListener('keydown', UTILS.checkTrusted(keyDown));
            function keyUp(event) {
                if (player && player.alive) {
                    var keyNum = event.which||event.keyCode||0;
                    if (keyNum == 13) {
                        toggleChat();
                    } else if (keysActive()) {
                        if (keys[keyNum]) {
                            keys[keyNum] = 0;
                            if (moveKeys[keyNum]) {
                                sendMoveDir();
                            } else if (keyNum == 32) {
                                attackState = 0;
                                sendAtckState();
                            }
                        }
                    }
                }
            }
            window.addEventListener('keyup',  UTILS.checkTrusted(keyUp));
            function sendAtckState() {
                if (player && player.alive) {
                    io.send("c", attackState, (player.buildIndex >= 0?getAttackDir():null));
                }
            }
            var lastMoveDir = undefined;
            function sendMoveDir() {
                var newMoveDir = getMoveDir();
                if (lastMoveDir == undefined || newMoveDir == undefined || Math.abs(newMoveDir - lastMoveDir) > 0.3) {
                    io.send("33", newMoveDir);
                    lastMoveDir = newMoveDir;
                }
            }
            function sendLockDir() {
                player.lockDir = player.lockDir?0:1;
                io.send("7", 0);
            }
            function sendMapPing() {
                io.send("14", 1);
            }
            function sendAutoGather() {
                io.send("7", 1);
            }
            function selectToBuild(index, wpn) {
                io.send("5", index, wpn);
            }

            // ENTER GAME:
            function enterGame() {
                window.FRVR && window.FRVR.tracker.levelStart("game_start")

                const sandbox = window.location.host.includes("sandbox");

                if(sandbox) {
                    let elements = document.getElementsByClassName("resourceDisplay");
                    let index = 0, length = elements.length;

                    while(index < length) {
                        const element = elements[index];

                        if(element) {
                            element.style.display = "block";
                            if(element.id !== "killCounter") element.style.display = "none";
                        }

                        index += 1;
                    }
                }

                saveVal("moo_name", nameInput.value);
                if (!inGame && socketReady()) {
                    inGame = true;
                    Sound.stop("menu");
                    showLoadingText("Loading...");
                    io.send("sp", {
                        name: nameInput.value,
                        moofoll: moofoll,
                        skin: skinColor
                    });
                }
            }

            // SETUP GAME:
            var firstSetup = true;
            function setupGame(yourSID) {
                loadingText.style.display = "none";
                menuCardHolder.style.display = "block";
                mainMenu.style.display = "none";
                keys = {};
                playerSID = yourSID;
                attackState = 0;
                inGame = true;
                //if (firstSetup) {
                firstSetup = false;
                gameObjects.length = 0;
                //}
            }

            // SHOW ANIM TEXT:
            function showText(x, y, value, obj, safe) {
                if(stackTexts) {
                    const north = value >= 0 ? "plus" : "minus";

                    if(typeof safe !== "bigint") {
                        if(north === "minus") return; // remove heal text

                        let similar = storage.findIndex(text => text.function == showText && text.data[0] === x && text.data[1] === y);

                        if(similar > -1) {
                            let item = storage[similar];

                            item.data[3][north] += value;

                            return;
                        }

                        const obj = {
                            plus: Math.max(value, 0),
                            minus: Math.min(value, 0)
                        };

                        return storage.push({function: showText, data: [x, y, value, obj]});
                    }

                    value = obj[north];
                }

                const textSize = Math.abs(value).toString().length * 25 * (value / 100);

                textManager.showText(x, y, textSize, 0.18, 500, Math.abs(value), (value>=0)?"#fff":"#8ecc51");
            }

            // KILL PLAYER:
            var deathTextScale = 99999;
            function killPlayer() {
                inGame = false;
                try {
                    factorem.refreshAds([2], true);
                } catch (e) {};
                gameUI.style.display = "none";
                hideAllWindows();
                lastDeath = {
                    x: player.x,
                    y: player.y
                };
                loadingText.style.display = "none";
                diedText.style.display = "block";
                diedText.style.fontSize = "0px";
                deathTextScale = 0;
                setTimeout(function() {
                    menuCardHolder.style.display = "block";
                    mainMenu.style.display = "block";
                    // Sound.play("menu", 1, true);
                    diedText.style.display = "none";
                }, config.deathFadeout);
            }

            // KILL ALL OBJECTS BY A PLAYER:
            function killObjects(sid) {
                if (player) objectManager.removeAllItems(sid);
            }

            // KILL OBJECT:
            function killObject(sid) {
                objectManager.disableBySid(sid);
            }

            // UPDATE SCORE DISPLAY:
            function updateStatusDisplay() {
                scoreDisplay.innerText = player.points;
                foodDisplay.innerText = player.food;;
                woodDisplay.innerText = player.wood;
                stoneDisplay.innerText = player.stone;
                killCounter.innerText = player.kills;
            }

            // ICONS:
            var iconSprites = {};
            var icons = ["crown", "skull"];
            function loadIcons() {
                for (var i = 0; i < icons.length; ++i) {
                    var tmpSprite = new Image();
                    tmpSprite.onload = function() {
                        this.isLoaded = true;
                    };
                    tmpSprite.src = ".././img/icons/" + icons[i] + ".png";
                    iconSprites[icons[i]] = tmpSprite;
                }
            }

            // UPDATE UPGRADES:
            var tmpList = [];
            function updateUpgrades(points, age) {
                player.upgradePoints = points;
                player.upgrAge = age;
                if (points > 0) {
                    tmpList.length = 0;
                    UTILS.removeAllChildren(upgradeHolder);
                    for (var i = 0; i < items.weapons.length; ++i) {
                        if (items.weapons[i].age == age && (items.weapons[i].pre == undefined || player.weapons.indexOf(items.weapons[i].pre) >= 0)) {
                            var e = UTILS.generateElement({
                                id: "upgradeItem" + i,
                                class: "actionBarItem",
                                onmouseout: function() { showItemInfo(); },
                                parent: upgradeHolder
                            });
                            e.style.backgroundImage = document.getElementById("actionBarItem" + i).style.backgroundImage;
                            tmpList.push(i);
                        }
                    }
                    for (var i = 0; i < items.list.length; ++i) {
                        if (items.list[i].age == age && (items.list[i].pre == undefined || player.items.indexOf(items.list[i].pre) >= 0)) {
                            var tmpI = (items.weapons.length + i);
                            var e = UTILS.generateElement({
                                id: "upgradeItem" + tmpI,
                                class: "actionBarItem",
                                onmouseout: function() { showItemInfo(); },
                                parent: upgradeHolder
                            });
                            e.style.backgroundImage = document.getElementById("actionBarItem" + tmpI).style.backgroundImage;
                            tmpList.push(tmpI);
                        }
                    }
                    for (var i = 0; i < tmpList.length; i++) {
                        (function(i) {
                            var tmpItem = document.getElementById('upgradeItem' + i);
                            tmpItem.onmouseover = function() {
                                if (items.weapons[i]) {
                                    showItemInfo(items.weapons[i], true);
                                } else {
                                    showItemInfo(items.list[i-items.weapons.length]);
                                }
                            };
                            tmpItem.onclick = UTILS.checkTrusted(function() {
                                io.send("6", i);
                            });
                            UTILS.hookTouchEvents(tmpItem);
                        })(tmpList[i]);
                    }
                    if (tmpList.length) {
                        upgradeHolder.style.display = "block";
                        upgradeCounter.style.display = "block";
                        upgradeCounter.innerHTML = "SELECT ITEMS (" + points + ")";
                    } else {
                        upgradeHolder.style.display = "none";
                        upgradeCounter.style.display = "none";
                        showItemInfo();
                    }
                } else {
                    upgradeHolder.style.display = "none";
                    upgradeCounter.style.display = "none";
                    showItemInfo();
                }
            }
            function sendUpgrade(index) {
                io.send("6", index);
            }

            // UPDATE AGE:
            function updateAge(xp, mxp, age) {
                if (xp != undefined)
                    player.XP = xp;
                if (mxp != undefined)
                    player.maxXP = mxp;
                if (age != undefined)
                    player.age = age;
                if (age == config.maxAge) {
                    ageText.innerHTML = "MAX AGE";
                    ageBarBody.style.width = "100%";
                } else {
                    ageText.innerHTML = "AGE " + player.age;
                    ageBarBody.style.width = ((player.XP/player.maxXP) * 100) + "%";
                }
            }

            // UPDATE LEADERBOARD:
            function updateLeaderboard(data) {
                UTILS.removeAllChildren(leaderboardData);
                var tmpC = 1;
                for (var i = 0; i < data.length; i += 3) {
                    (function(i) {
                        UTILS.generateElement({
                            class: "leaderHolder",
                            parent: leaderboardData,
                            children: [
                                UTILS.generateElement({
                                    class: "leaderboardItem",
                                    style: "color:" + ((data[i] == playerSID) ? "#fff" : "rgba(255,255,255,0.6)"),
                                    text: tmpC + ". " + (data[i+1] != "" ? data[i+1] : "unknown")
                                }),
                                UTILS.generateElement({
                                    class: "leaderScore",
                                    text: UTILS.kFormat(data[i+2]) || "0"
                                })
                            ]
                        });
                    })(i);
                    tmpC++;
                }
            }

            const Volcano = {
                animationTime: 0,
                land: null,
                lava: null,
                x: config.volcano.x,
                y: config.volcano.y
            };

            const renderVolcano = (x, y) => {
                const circleScale = config.volcanoScale * 3.2;

                mainContext.beginPath();
                mainContext.strokeStyle = "red";
                mainContext.globalAlpha = 0.2;

                mainContext.arc(config.volcano.x - x, config.volcano.y - y, circleScale, 0, Math.PI * 2, false);
                mainContext.fill();

                mainContext.closePath();
            }

            const drawBar = (be, tmpObj, x, y, color, min, limit, width = 0) => {
                be.fillStyle = darkOutlineColor;
                be.roundRect(x - config.healthBarWidth - config.healthBarPad, y + tmpObj.scale + config.nameY, 2 * (config.healthBarWidth + width) + 2 * config.healthBarPad, 17, 8);
                be.fill();
                be.fillStyle = color;
                be.roundRect(x - config.healthBarWidth, y + tmpObj.scale + config.nameY + config.healthBarPad, 2 * (config.healthBarWidth + width) * (min / limit), 17 - 2 * config.healthBarPad, 7);
                be.fill();

                if(tmpObj.isItem) {
                    be.fillStyle = "#fff";
                    be.lineJoin = "round";
                    be.font = "8px Hammersmith One";
                    be.strokeStyle = darkOutlineColor;
                    be.lineWidth = 3;
                    be.strokeText(`HP: ${Math.floor(min)}/${Math.floor(limit)}`,x,y + tmpObj.scale + config.nameY + config.healthBarPad - 2);
                    be.fillText(`HP: ${Math.floor(min)}/${Math.floor(limit)}`,x,y + tmpObj.scale + config.nameY + config.healthBarPad - 2);
                }
            }

            // UPDATE GAME:
            function updateGame() {
                if (true) {

                    // UPDATE DIRECTION:
                    if (player) {
                        if (!lastSent || now - lastSent >= (1000 / config.clientSendRate)) {
                            lastSent = now;
                            io.send("2", getAttackDir());
                        }
                    }

                    // DEATH TEXT:
                    if (deathTextScale < 120) {
                        deathTextScale += 0.1 * delta;
                        diedText.style.fontSize = Math.min(Math.round(deathTextScale), 120) + "px";
                    }

                    // MOVE CAMERA:
                    if (player) {
                        var tmpDist = UTILS.getDistance(camX, camY, player.x, player.y);
                        var tmpDir = UTILS.getDirection(player.x, player.y, camX, camY);
                        var camSpd = Math.min(tmpDist * 0.01 * delta, tmpDist);
                        if (tmpDist > 0.05) {
                            camX += camSpd * Math.cos(tmpDir);
                            camY += camSpd * Math.sin(tmpDir);
                        } else {
                            camX = player.x;
                            camY = player.y;
                        }
                    } else {
                        camX = config.mapScale / 2;
                        camY = config.mapScale / 2;
                    }

                    // INTERPOLATE PLAYERS AND AI:
                    var lastTime = now - (1000 / config.serverUpdateRate);
                    var tmpDiff;
                    for (var i = 0; i < players.length + ais.length; ++i) {
                        tmpObj = players[i]||ais[i-players.length];
                        if (tmpObj && tmpObj.visible) {
                            if (tmpObj.forcePos) {
                                tmpObj.x = tmpObj.x2;
                                tmpObj.y = tmpObj.y2;
                                tmpObj.dir = tmpObj.d2;
                            } else {
                                var total = tmpObj.t2 - tmpObj.t1;
                                var fraction = lastTime - tmpObj.t1;
                                var ratio = (fraction / total);
                                var rate = 170;
                                tmpObj.dt += delta;
                                var tmpRate = Math.min(1.7, tmpObj.dt / rate);
                                var tmpDiff = (tmpObj.x2 - tmpObj.x1);
                                tmpObj.x = tmpObj.x1 + (tmpDiff * tmpRate);
                                tmpDiff = (tmpObj.y2 - tmpObj.y1);
                                tmpObj.y = tmpObj.y1 + (tmpDiff * tmpRate);
                                tmpObj.dir = Math.lerpAngle(tmpObj.d2, tmpObj.d1, Math.min(1.2, ratio));
                            }
                        }
                    }

                    // RENDER CORDS:
                    var xOffset = camX - (maxScreenWidth / 2);
                    var yOffset = camY - (maxScreenHeight / 2);

                    // RENDER BACKGROUND:
                    if (config.snowBiomeTop - yOffset <= 0 && config.mapScale - config.snowBiomeTop - yOffset >= maxScreenHeight) {
                        mainContext.fillStyle = "#b6db66";
                        mainContext.fillRect(0, 0, maxScreenWidth, maxScreenHeight);
                    } else if (config.mapScale - config.snowBiomeTop - yOffset <= 0) {
                        mainContext.fillStyle = "#dbc666";
                        mainContext.fillRect(0, 0, maxScreenWidth, maxScreenHeight);
                    } else if (config.snowBiomeTop - yOffset >= maxScreenHeight) {
                        mainContext.fillStyle = "#fff";
                        mainContext.fillRect(0, 0, maxScreenWidth, maxScreenHeight);
                    } else if (config.snowBiomeTop - yOffset >= 0) {
                        mainContext.fillStyle = "#fff";
                        mainContext.fillRect(0, 0, maxScreenWidth, config.snowBiomeTop - yOffset);
                        mainContext.fillStyle = "#b6db66";
                        mainContext.fillRect(0, config.snowBiomeTop - yOffset, maxScreenWidth,
                                             maxScreenHeight - (config.snowBiomeTop - yOffset));
                    } else {
                        mainContext.fillStyle = "#b6db66";
                        mainContext.fillRect(0, 0, maxScreenWidth,
                                             (config.mapScale - config.snowBiomeTop - yOffset));
                        mainContext.fillStyle = "#dbc666";
                        mainContext.fillRect(0, (config.mapScale - config.snowBiomeTop - yOffset), maxScreenWidth,
                                             maxScreenHeight - (config.mapScale - config.snowBiomeTop - yOffset));
                    }

                    // RENDER WATER AREAS:
                    if (!firstSetup) {
                        waterMult += waterPlus * config.waveSpeed * delta;
                        if (waterMult >= config.waveMax) {
                            waterMult = config.waveMax;
                            waterPlus = -1;
                        } else if (waterMult <= 1) {
                            waterMult = waterPlus = 1;
                        }
                        mainContext.globalAlpha = 1;
                        mainContext.fillStyle = "#dbc666";
                        renderWaterBodies(xOffset, yOffset, mainContext, config.riverPadding);
                        mainContext.fillStyle = "#91b2db";
                        renderWaterBodies(xOffset, yOffset, mainContext, (waterMult - 1) * 250);
                    }

                    // RENDER VOLCANO:
                    mainContext.beginPath();

                    const lineWidth = 20;

                    mainContext.lineWidth = lineWidth;
                    mainContext.strokeStyle = "red";
                    mainContext.globalAlpha = 0.2;

                    mainContext.moveTo(12400 - xOffset, 12400 - yOffset);
                    mainContext.lineTo(14400 - xOffset, 12400 - yOffset);

                    mainContext.moveTo(12400 - xOffset, 12400 - lineWidth/2 - yOffset);
                    mainContext.lineTo(12400 - xOffset, 14400 - yOffset);

                    mainContext.stroke();
                    mainContext.closePath();

                    renderVolcano(xOffset, yOffset);

                    // RENDER GRID:
                    if(gridSize) {
                        mainContext.lineWidth = 4;
                        mainContext.strokeStyle = "#000";
                        mainContext.globalAlpha = 0.06;
                        mainContext.beginPath();
                        for (var x = -camX; x < maxScreenWidth; x += maxScreenHeight / gridSize) {
                            if (x > 0) {
                                mainContext.moveTo(x, 0);
                                mainContext.lineTo(x, maxScreenHeight);
                            }
                        }
                        for (var y = -camY; y < maxScreenHeight; y += maxScreenHeight / gridSize) {
                            if (x > 0) {
                                mainContext.moveTo(0, y);
                                mainContext.lineTo(maxScreenWidth, y);
                            }
                        }
                        mainContext.stroke();
                    }

                    // RENDER BOTTOM LAYER:
                    mainContext.globalAlpha = 1;
                    mainContext.strokeStyle = outlineColor;
                    renderGameObjects(-1, xOffset, yOffset);

                    // RENDER PROJECTILES:
                    mainContext.globalAlpha = 1;
                    mainContext.lineWidth = outlineWidth;
                    renderProjectiles(0, xOffset, yOffset);

                    // RENDER PLAYERS:
                    renderPlayers(xOffset, yOffset, 0);

                    // RENDER AI:
                    mainContext.globalAlpha = 1;
                    for (var i = 0; i < ais.length; ++i) {
                        tmpObj = ais[i];
                        if (tmpObj.active && tmpObj.visible) {
                            tmpObj.animate(delta);
                            mainContext.save();
                            mainContext.translate(tmpObj.x - xOffset, tmpObj.y - yOffset);
                            mainContext.rotate(tmpObj.dir+tmpObj.dirPlus-(Math.PI/2));
                            renderAI(tmpObj, mainContext);
                            mainContext.restore();
                        }
                    }

                    // RENDER GAME OBJECTS (LAYERED):
                    renderGameObjects(0, xOffset, yOffset);
                    renderProjectiles(1, xOffset, yOffset);
                    renderGameObjects(1, xOffset, yOffset);
                    renderPlayers(xOffset, yOffset, 1);
                    renderGameObjects(2, xOffset, yOffset);
                    renderGameObjects(3, xOffset, yOffset);

                    // MAP BOUNDARIES:
                    mainContext.fillStyle = "#000";
                    mainContext.globalAlpha = 0.09;
                    if (xOffset <= 0) {
                        mainContext.fillRect(0, 0, -xOffset, maxScreenHeight);
                    } if (config.mapScale - xOffset <= maxScreenWidth) {
                        var tmpY = Math.max(0, -yOffset);
                        mainContext.fillRect(config.mapScale - xOffset, tmpY, maxScreenWidth - (config.mapScale - xOffset), maxScreenHeight - tmpY);
                    } if (yOffset <= 0) {
                        mainContext.fillRect(-xOffset, 0, maxScreenWidth + xOffset, -yOffset);
                    } if (config.mapScale - yOffset <= maxScreenHeight) {
                        var tmpX = Math.max(0, -xOffset);
                        var tmpMin = 0;
                        if (config.mapScale - xOffset <= maxScreenWidth)
                            tmpMin = maxScreenWidth - (config.mapScale - xOffset);
                        mainContext.fillRect(tmpX, config.mapScale - yOffset,
                                             (maxScreenWidth - tmpX) - tmpMin, maxScreenHeight - (config.mapScale - yOffset));
                    }

                    // RENDER DAY/NIGHT TIME:
                    mainContext.globalAlpha = 1;
                    mainContext.fillStyle = "rgba(0, 0, 70, 0.35)";
                    mainContext.fillRect(0, 0, maxScreenWidth, maxScreenHeight);

                    // RENDER PLAYER AND AI UI:
                    mainContext.strokeStyle = darkOutlineColor;
                    for (var i = 0; i < players.length + ais.length; ++i) {
                        tmpObj = players[i]||ais[i-players.length];
                        if (tmpObj.visible) {

                            // NAME AND HEALTH:
                            if (tmpObj.skinIndex != 10 || (tmpObj==player) || (tmpObj.team && tmpObj.team==player.team)) {
                                var tmpText = (tmpObj.team?"["+tmpObj.team+"] ":"")+(tmpObj.name||"");
                                if (tmpText != "") {
                                    mainContext.font = (tmpObj.nameScale||30) + "px Hammersmith One";
                                    mainContext.fillStyle = "#fff";
                                    mainContext.textBaseline = "middle";
                                    mainContext.textAlign = "center";
                                    mainContext.lineWidth = (tmpObj.nameScale?11:8);
                                    mainContext.lineJoin = "round";
                                    mainContext.strokeText(tmpText, tmpObj.x - xOffset, (tmpObj.y - yOffset - tmpObj.scale) - config.nameY);
                                    mainContext.fillText(tmpText, tmpObj.x - xOffset, (tmpObj.y - yOffset - tmpObj.scale) - config.nameY);
                                    if (tmpObj.isLeader && iconSprites["crown"].isLoaded) {
                                        var tmpS = config.crownIconScale;
                                        var tmpX = tmpObj.x - xOffset - (tmpS/2) - (mainContext.measureText(tmpText).width / 2) - config.crownPad;
                                        mainContext.drawImage(iconSprites["crown"], tmpX, (tmpObj.y - yOffset - tmpObj.scale)
                                                              - config.nameY - (tmpS/2) - 5, tmpS, tmpS);
                                    } if (tmpObj.iconIndex == 1 && iconSprites["skull"].isLoaded) {
                                        var tmpS = config.crownIconScale;
                                        var tmpX = tmpObj.x - xOffset - (tmpS/2) + (mainContext.measureText(tmpText).width / 2) + config.crownPad;
                                        mainContext.drawImage(iconSprites["skull"], tmpX, (tmpObj.y - yOffset - tmpObj.scale)
                                                              - config.nameY - (tmpS/2) - 5, tmpS, tmpS);
                                    }
                                } if (tmpObj.health > 0) {
                                    const peace = (tmpObj==player||(tmpObj.team&&tmpObj.team==player.team));

                                    // HEALTH BAR:
                                    let color = peace ? "#8ecc51" : "#cc5151",
                                        fillCount = tmpObj.health,
                                        fillLimit = tmpObj.maxHealth;

                                    drawBar(mainContext, tmpObj, tmpObj.x - xOffset, tmpObj.y - yOffset - tmpObj.scale - config.nameY + 75, color, fillCount, fillLimit);

                                    if(tmpObj.isPlayer) {
                                        // PRIMARY BAR:
                                        color = tmpObj.reloads[0].ready ? (peace ? "#8ecc51" : "#cc5151") : (peace ? "#b7cc51" : "#b66868");
                                        fillCount = Math.min(tmpObj.reloads[0].limit_, Date.now() - (tmpObj.reloads[0].date || 0));
                                        fillLimit = tmpObj.reloads[0].limit_;

                                        drawBar(mainContext, tmpObj, tmpObj.x - xOffset, tmpObj.y - yOffset - tmpObj.scale - config.nameY + 60, color, fillCount, fillLimit, -26.5);

                                        // SECONDARY BAR:
                                        color = tmpObj.reloads[1].ready ? (peace ? "#8ecc51" : "#cc5151") : (peace ? "#b7cc51" : "#b66868");
                                        fillCount = Math.min(tmpObj.reloads[1].limit_, Date.now() - (tmpObj.reloads[1].date || 0));
                                        fillLimit = tmpObj.reloads[1].limit_;

                                        drawBar(mainContext, tmpObj, tmpObj.x - xOffset + 52.5, tmpObj.y - yOffset - tmpObj.scale - config.nameY + 60, color, fillCount, fillLimit, -26.5);
                                    }
                                }
                            }
                        }
                    }

                    // RENDER ANIM TEXTS:
                    textManager.update(delta, mainContext, xOffset, yOffset);

                    // RENDER CHAT MESSAGES:
                    for (var i = 0; i < players.length; ++i) {
                        tmpObj = players[i];
                        if (tmpObj.visible && tmpObj.chatCountdown > 0) {
                            tmpObj.chatCountdown -= delta;
                            if (tmpObj.chatCountdown <= 0)
                                tmpObj.chatCountdown = 0;
                            mainContext.font = "32px Hammersmith One";
                            var tmpSize = mainContext.measureText(tmpObj.chatMessage);
                            mainContext.textBaseline = "middle";
                            mainContext.textAlign = "center";
                            var tmpX = tmpObj.x - xOffset;
                            var tmpY = tmpObj.y - tmpObj.scale - yOffset - 90;
                            var tmpH = 47;
                            var tmpW = tmpSize.width + 17;
                            mainContext.fillStyle = "rgba(0,0,0,0.2)";
                            mainContext.roundRect(tmpX-tmpW/2, tmpY-tmpH/2, tmpW, tmpH, 6);
                            mainContext.fill();
                            mainContext.fillStyle = "#fff";
                            mainContext.fillText(tmpObj.chatMessage, tmpX, tmpY);
                        }
                    }
                }

                // RENDER MINIMAP:
                renderMinimap(delta);

                // RENDER CONTROLS:
                if (controllingTouch.id !== -1) {
                    renderControl(
                        controllingTouch.startX, controllingTouch.startY,
                        controllingTouch.currentX, controllingTouch.currentY
                    );
                }
                if (attackingTouch.id !== -1) {
                    renderControl(
                        attackingTouch.startX, attackingTouch.startY,
                        attackingTouch.currentX, attackingTouch.currentY
                    );
                }
            }

            // RENDER CONTROL:
            function renderControl(startX, startY, currentX, currentY) {
                mainContext.save();
                mainContext.setTransform(1, 0, 0, 1, 0, 0);
                // mainContext.resetTransform();
                mainContext.scale(pixelDensity, pixelDensity);
                var controlRadius = 50;
                mainContext.beginPath();
                mainContext.arc(startX, startY, controlRadius, 0, Math.PI * 2, false);
                mainContext.closePath();
                mainContext.fillStyle = "rgba(255, 255, 255, 0.3)";
                mainContext.fill();
                var controlRadius = 50;
                var offsetX = currentX - startX;
                var offsetY = currentY - startY;
                var mag = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2));
                var divisor = mag > controlRadius ? (mag / controlRadius) : 1;
                offsetX /= divisor;
                offsetY /= divisor;
                mainContext.beginPath();
                mainContext.arc(startX + offsetX, startY + offsetY, controlRadius * 0.5, 0, Math.PI * 2, false);
                mainContext.closePath();
                mainContext.fillStyle = "white";
                mainContext.fill();
                mainContext.restore();
            }

            // RENDER PROJECTILES:
            function renderProjectiles(layer, xOffset, yOffset) {
                for (var i = 0; i < projectiles.length; ++i) {
                    tmpObj = projectiles[i];
                    if (tmpObj.active && tmpObj.layer == layer) {
                        tmpObj.update(delta);
                        if (tmpObj.active && isOnScreen(tmpObj.x-xOffset, tmpObj.y-yOffset, tmpObj.scale)) {
                            mainContext.save();
                            mainContext.translate(tmpObj.x - xOffset, tmpObj.y - yOffset);
                            mainContext.rotate(tmpObj.dir);
                            renderProjectile(0, 0, tmpObj, mainContext, 1);
                            mainContext.restore();
                        }
                    }
                }
            }

            // RENDER PROJECTILE:
            var projectileSprites = {};
            function renderProjectile(x, y, obj, ctxt, debug) {
                if (obj.src) {
                    var tmpSrc = items.projectiles[obj.indx].src;
                    var tmpSprite = projectileSprites[tmpSrc];
                    if (!tmpSprite) {
                        tmpSprite = new Image();
                        tmpSprite.onload = function() {
                            this.isLoaded = true;
                        }
                        tmpSprite.src = ".././img/weapons/" + tmpSrc + ".png";
                        projectileSprites[tmpSrc] = tmpSprite;
                    }
                    if (tmpSprite.isLoaded)
                        ctxt.drawImage(tmpSprite, x - (obj.scale / 2), y - (obj.scale / 2), obj.scale, obj.scale);
                } else if (obj.indx == 1) {
                    ctxt.fillStyle = "#939393";
                    renderCircle(x, y, obj.scale, ctxt);
                }
            }

            // RENDER WATER BODIES:
            function renderWaterBodies(xOffset, yOffset, ctxt, padding) {

                // MIDDLE RIVER:
                var tmpW = config.riverWidth + padding;
                var tmpY = (config.mapScale / 2) - yOffset - (tmpW / 2);
                if (tmpY < maxScreenHeight && tmpY + tmpW > 0) {
                    ctxt.fillRect(0, tmpY, maxScreenWidth, tmpW);
                }
            }

            // RENDER GAME OBJECTS:
            function renderGameObjects(layer, xOffset, yOffset) {
                var tmpSprite, tmpX, tmpY;
                for (var i = 0; i < gameObjects.length; ++i) {
                    tmpObj = gameObjects[i];
                    if (tmpObj.active) {
                        tmpX = tmpObj.x + tmpObj.xWiggle - xOffset;
                        tmpY = tmpObj.y + tmpObj.yWiggle - yOffset;
                        if (layer == 0) {
                            tmpObj.update(delta);
                        }
                        if (tmpObj.layer == layer && isOnScreen(tmpX, tmpY, tmpObj.scale + (tmpObj.blocker||0))) {
                            mainContext.globalAlpha = tmpObj.hideFromEnemy?0.6:1;
                            if (tmpObj.isItem) {
                                tmpSprite = getItemSprite(tmpObj);
                                mainContext.save();
                                mainContext.translate(tmpX, tmpY);
                                mainContext.rotate(tmpObj.dir);
                                mainContext.drawImage(tmpSprite, -(tmpSprite.width / 2), -(tmpSprite.height / 2));
                                if (tmpObj.blocker) {
                                    mainContext.strokeStyle = "#db6e6e";
                                    mainContext.globalAlpha = 0.3;
                                    mainContext.lineWidth = 6;
                                    renderCircle(0, 0, tmpObj.blocker, mainContext, false, true);
                                }

                                mainContext.restore();

                                // HEALTH BAR:
                                let color = ally(tmpObj.owner.sid) ? "#8ecc51" : "#cc5151",
                                    fillCount = tmpObj.health,
                                    fillLimit = tmpObj.maxHealth;

                                drawBar(mainContext, tmpObj, tmpObj.x - xOffset, tmpObj.y - yOffset - tmpObj.scale - config.nameY + 75, color, fillCount, fillLimit);
                            } else {
                                tmpSprite = getResSprite(tmpObj);
                                mainContext.drawImage(tmpSprite, tmpX - (tmpSprite.width / 2), tmpY - (tmpSprite.height / 2));
                            }
                        }
                    }
                }
            }

            // GATHER ANIMATION:
            function gatherAnimation(sid, didHit, index, safe) {
                if(typeof safe !== "bigint") return storage.push({function: gatherAnimation, data: arguments});

                tmpObj = findPlayerBySID(sid);
                if (tmpObj) tmpObj.startAnim(didHit, index);
            }

            // RENDER PLAYERS:
            function renderPlayers(xOffset, yOffset, zIndex) {
                mainContext.globalAlpha = 1;
                for (var i = 0; i < players.length; ++i) {
                    tmpObj = players[i];
                    if (tmpObj.zIndex == zIndex) {
                        tmpObj.animate(delta);
                        if (tmpObj.visible) {
                            tmpObj.skinRot += (0.002 * delta);
                            tmpDir = ((tmpObj == player)?getAttackDir():tmpObj.dir) + tmpObj.dirPlus;
                            mainContext.save();
                            mainContext.translate(tmpObj.x - xOffset, tmpObj.y - yOffset);

                            // RENDER PLAYER:
                            mainContext.rotate(tmpDir);
                            renderPlayer(tmpObj, mainContext);
                            mainContext.restore();
                        }
                    }
                }
            }

            // RENDER PLAYER:
            function renderPlayer(obj, ctxt) {
                ctxt = ctxt || mainContext;
                ctxt.lineWidth = outlineWidth;
                ctxt.lineJoin = "miter";
                var handAngle = (Math.PI / 4) * (items.weapons[obj.weaponIndex].armS||1);
                var oHandAngle = (obj.buildIndex < 0)?(items.weapons[obj.weaponIndex].hndS||1):1;
                var oHandDist = (obj.buildIndex < 0)?(items.weapons[obj.weaponIndex].hndD||1):1;

                // TAIL/CAPE:
                if (obj.tailIndex > 0) {
                    renderTail(obj.tailIndex, ctxt, obj);
                }

                // WEAPON BELLOW HANDS:
                if (obj.buildIndex < 0 && !items.weapons[obj.weaponIndex].aboveHand) {
                    renderTool(items.weapons[obj.weaponIndex], config.weaponVariants[obj.weaponVariant].src, obj.scale, 0, ctxt);
                    if (items.weapons[obj.weaponIndex].projectile != undefined && !items.weapons[obj.weaponIndex].hideProjectile) {
                        renderProjectile(obj.scale, 0,
                                         items.projectiles[items.weapons[obj.weaponIndex].projectile], mainContext);
                    }
                }

                // HANDS:
                ctxt.fillStyle = config.skinColors[obj.skinColor];
                renderCircle(obj.scale * Math.cos(handAngle), (obj.scale * Math.sin(handAngle)), 14);
                renderCircle((obj.scale * oHandDist) * Math.cos(-handAngle * oHandAngle),
                             (obj.scale * oHandDist) * Math.sin(-handAngle * oHandAngle), 14);

                // WEAPON ABOVE HANDS:
                if (obj.buildIndex < 0 && items.weapons[obj.weaponIndex].aboveHand) {
                    renderTool(items.weapons[obj.weaponIndex], config.weaponVariants[obj.weaponVariant].src, obj.scale, 0, ctxt);
                    if (items.weapons[obj.weaponIndex].projectile != undefined && !items.weapons[obj.weaponIndex].hideProjectile) {
                        renderProjectile(obj.scale, 0,
                                         items.projectiles[items.weapons[obj.weaponIndex].projectile], mainContext);
                    }
                }

                // BUILD ITEM:
                if (obj.buildIndex >= 0) {
                    var tmpSprite = getItemSprite(items.list[obj.buildIndex]);
                    ctxt.drawImage(tmpSprite, obj.scale - items.list[obj.buildIndex].holdOffset, -tmpSprite.width / 2);
                }

                // BODY:
                renderCircle(0, 0, obj.scale, ctxt);

                // SKIN:
                if (obj.skinIndex > 0) {
                    ctxt.rotate(Math.PI/2);
                    renderSkin(obj.skinIndex, ctxt, null, obj);
                }
            }

            // RENDER SKINS:
            var skinSprites = {};
            var skinPointers = {};
            var tmpSkin;
            function renderSkin(index, ctxt, parentSkin, owner) {
                tmpSkin = skinSprites[index];
                if (!tmpSkin) {
                    var tmpImage = new Image();
                    tmpImage.onload = function() {
                        this.isLoaded = true;
                        this.onload = null;
                    };
                    tmpImage.src = ".././img/hats/hat_" + index + ".png";
                    skinSprites[index] = tmpImage;
                    tmpSkin = tmpImage;
                }
                var tmpObj = parentSkin||skinPointers[index];
                if (!tmpObj) {
                    for (var i = 0; i < hats.length; ++i) {
                        if (hats[i].id == index) {
                            tmpObj = hats[i];
                            break;
                        }
                    }
                    skinPointers[index] = tmpObj;
                }
                if (tmpSkin.isLoaded)
                    ctxt.drawImage(tmpSkin, -tmpObj.scale/2, -tmpObj.scale/2, tmpObj.scale, tmpObj.scale);
                if (!parentSkin && tmpObj.topSprite) {
                    ctxt.save();
                    ctxt.rotate(owner.skinRot);
                    renderSkin(index + "_top", ctxt, tmpObj, owner);
                    ctxt.restore();
                }
            }

            // RENDER TAIL:
            var accessSprites = {};
            var accessPointers = {};
            function renderTail(index, ctxt, owner) {
                tmpSkin = accessSprites[index];
                if (!tmpSkin) {
                    var tmpImage = new Image();
                    tmpImage.onload = function() {
                        this.isLoaded = true;
                        this.onload = null;
                    };
                    tmpImage.src = ".././img/accessories/access_" + index + ".png";
                    accessSprites[index] = tmpImage;
                    tmpSkin = tmpImage;
                }
                var tmpObj = accessPointers[index];
                if (!tmpObj) {
                    for (var i = 0; i < accessories.length; ++i) {
                        if (accessories[i].id == index) {
                            tmpObj = accessories[i];
                            break;
                        }
                    }
                    accessPointers[index] = tmpObj;
                }
                if (tmpSkin.isLoaded) {
                    ctxt.save();
                    ctxt.translate(-20 - (tmpObj.xOff||0), 0);
                    if (tmpObj.spin)
                        ctxt.rotate(owner.skinRot);
                    ctxt.drawImage(tmpSkin, -(tmpObj.scale/2), -(tmpObj.scale/2), tmpObj.scale, tmpObj.scale);
                    ctxt.restore();
                }
            }

            // RENDER TOOL:
            var toolSprites = {};
            function renderTool(obj, variant, x, y, ctxt) {
                var tmpSrc = obj.src + (variant||"");
                var tmpSprite = toolSprites[tmpSrc];
                if (!tmpSprite) {
                    tmpSprite = new Image();
                    tmpSprite.onload = function() {
                        this.isLoaded = true;
                    }
                    tmpSprite.src = ".././img/weapons/" + tmpSrc + ".png";
                    toolSprites[tmpSrc] = tmpSprite;
                }
                if (tmpSprite.isLoaded)
                    ctxt.drawImage(tmpSprite, x+obj.xOff-(obj.length/2), y+obj.yOff-(obj.width/2), obj.length, obj.width);
            }

            // RENDER GAME OBJECTS:
            var gameObjectSprites = {};
            function getResSprite(obj) {
                var biomeID = (obj.y>=config.mapScale-config.snowBiomeTop)?2:((obj.y<=config.snowBiomeTop)?1:0);
                var tmpIndex = (obj.type + "_" + obj.scale + "_" + biomeID);
                var tmpSprite = gameObjectSprites[tmpIndex];
                if (!tmpSprite) {
                    var tmpCanvas = document.createElement('canvas');
                    tmpCanvas.width = tmpCanvas.height = (obj.scale * 2.1) + outlineWidth;
                    var tmpContext = tmpCanvas.getContext('2d');
                    tmpContext.translate((tmpCanvas.width / 2), (tmpCanvas.height / 2));
                    tmpContext.rotate(UTILS.randFloat(0, Math.PI));
                    tmpContext.strokeStyle = outlineColor;
                    tmpContext.lineWidth = outlineWidth;
                    if (obj.type == 0) {
                        var tmpScale;
                        for (var i = 0; i < 2; ++i) {
                            tmpScale = tmpObj.scale * (!i?1:0.5);
                            renderStar(tmpContext, 7, tmpScale, tmpScale * 0.7);
                            tmpContext.fillStyle = !biomeID?(!i?"#9ebf57":"#b4db62"):(!i?"#e3f1f4":"#fff");
                            tmpContext.fill();
                            if (!i)
                                tmpContext.stroke();
                        }
                    } else if (obj.type == 1) {
                        if (biomeID == 2) {
                            tmpContext.fillStyle = "#606060";
                            renderStar(tmpContext, 6, obj.scale * 0.3, obj.scale * 0.71);
                            tmpContext.fill();
                            tmpContext.stroke();
                            tmpContext.fillStyle = "#89a54c";
                            renderCircle(0, 0, obj.scale * 0.55, tmpContext);
                            tmpContext.fillStyle = "#a5c65b";
                            renderCircle(0, 0, obj.scale * 0.3, tmpContext, true);
                        } else {
                            renderBlob(tmpContext, 6, tmpObj.scale, tmpObj.scale * 0.7);
                            tmpContext.fillStyle = biomeID?"#e3f1f4":"#89a54c";
                            tmpContext.fill();
                            tmpContext.stroke();
                            tmpContext.fillStyle = biomeID?"#6a64af":"#c15555";
                            var tmpRange;
                            var berries = 4;
                            var rotVal = mathPI2 / berries;
                            for (var i = 0; i < berries; ++i) {
                                tmpRange = UTILS.randInt(tmpObj.scale/3.5, tmpObj.scale/2.3);
                                renderCircle(tmpRange * Math.cos(rotVal * i), tmpRange * Math.sin(rotVal * i),
                                             UTILS.randInt(10, 12), tmpContext);
                            }
                        }
                    } else if (obj.type == 2 || obj.type == 3) {
                        tmpContext.fillStyle = (obj.type==2)?(biomeID==2?"#938d77":"#939393"):"#e0c655";
                        renderStar(tmpContext, 3, obj.scale, obj.scale);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = (obj.type==2)?(biomeID==2?"#b2ab90":"#bcbcbc"):"#ebdca3";
                        renderStar(tmpContext, 3, obj.scale * 0.55, obj.scale * 0.65);
                        tmpContext.fill();
                    }
                    tmpSprite = tmpCanvas;
                    gameObjectSprites[tmpIndex] = tmpSprite;
                }
                return tmpSprite;
            }

            // GET ITEM SPRITE:
            var itemSprites = [];
            function getItemSprite(obj, asIcon) {
                var tmpSprite = itemSprites[obj.id];
                if (!tmpSprite || asIcon) {
                    var tmpCanvas = document.createElement('canvas');
                    tmpCanvas.width = tmpCanvas.height = (obj.scale * 2.5) + outlineWidth +
                        (items.list[obj.id].spritePadding||0);
                    var tmpContext = tmpCanvas.getContext('2d');
                    tmpContext.translate((tmpCanvas.width / 2), (tmpCanvas.height / 2));
                    tmpContext.rotate(asIcon?0:(Math.PI/2));
                    tmpContext.strokeStyle = outlineColor;
                    tmpContext.lineWidth = outlineWidth * (asIcon?(tmpCanvas.width/81):1);
                    if (obj.name == "apple") {
                        tmpContext.fillStyle = "#c15555";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fillStyle = "#89a54c";
                        var leafDir = -(Math.PI / 2);
                        renderLeaf(obj.scale * Math.cos(leafDir), obj.scale * Math.sin(leafDir),
                                   25, leafDir + Math.PI/2, tmpContext);
                    } else if (obj.name == "cookie") {
                        tmpContext.fillStyle = "#cca861";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fillStyle = "#937c4b";
                        var chips = 4;
                        var rotVal = mathPI2 / chips;
                        var tmpRange;
                        for (var i = 0; i < chips; ++i) {
                            tmpRange = UTILS.randInt(obj.scale / 2.5, obj.scale / 1.7);
                            renderCircle(tmpRange * Math.cos(rotVal * i), tmpRange * Math.sin(rotVal * i),
                                         UTILS.randInt(4, 5), tmpContext, true);
                        }
                    } else if (obj.name == "cheese") {
                        tmpContext.fillStyle = "#f4f3ac";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fillStyle = "#c3c28b";
                        var chips = 4;
                        var rotVal = mathPI2 / chips;
                        var tmpRange;
                        for (var i = 0; i < chips; ++i) {
                            tmpRange = UTILS.randInt(obj.scale / 2.5, obj.scale / 1.7);
                            renderCircle(tmpRange * Math.cos(rotVal * i), tmpRange * Math.sin(rotVal * i),
                                         UTILS.randInt(4, 5), tmpContext, true);
                        }
                    } else if (obj.name == "wood wall" || obj.name == "stone wall" || obj.name == "castle wall") {
                        tmpContext.fillStyle = (obj.name == "castle wall")?"#83898e":(obj.name=="wood wall")?
                            "#a5974c":"#939393";
                        var sides = (obj.name == "castle wall")?4:3;
                        renderStar(tmpContext, sides, obj.scale * 1.1, obj.scale * 1.1);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = (obj.name == "castle wall")?"#9da4aa":(obj.name=="wood wall")?
                            "#c9b758":"#bcbcbc";
                        renderStar(tmpContext, sides, obj.scale * 0.65, obj.scale * 0.65);
                        tmpContext.fill();
                    } else if (obj.name == "spikes" || obj.name == "greater spikes" || obj.name == "poison spikes"
                               || obj.name == "spinning spikes") {
                        tmpContext.fillStyle = (obj.name == "poison spikes")?"#7b935d":"#939393";
                        var tmpScale = (obj.scale * 0.6);
                        renderStar(tmpContext, (obj.name == "spikes")?5:6, obj.scale, tmpScale);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#a5974c";
                        renderCircle(0, 0, tmpScale, tmpContext);
                        tmpContext.fillStyle = "#c9b758";
                        renderCircle(0, 0, tmpScale/2, tmpContext, true);
                    } else if (obj.name == "windmill" || obj.name == "faster windmill" || obj.name == "power mill") {
                        tmpContext.fillStyle = "#a5974c";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fillStyle = "#c9b758";
                        renderRectCircle(0, 0, obj.scale * 1.5, 29, 4, tmpContext);
                        tmpContext.fillStyle = "#a5974c";
                        renderCircle(0, 0, obj.scale * 0.5, tmpContext);
                    } else if (obj.name == "mine") {
                        tmpContext.fillStyle = "#939393";
                        renderStar(tmpContext, 3, obj.scale, obj.scale);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#bcbcbc";
                        renderStar(tmpContext, 3, obj.scale * 0.55, obj.scale * 0.65);
                        tmpContext.fill();
                    } else if (obj.name == "sapling") {
                        for (var i = 0; i < 2; ++i) {
                            var tmpScale = obj.scale * (!i?1:0.5);
                            renderStar(tmpContext, 7, tmpScale, tmpScale * 0.7);
                            tmpContext.fillStyle = (!i?"#9ebf57":"#b4db62");
                            tmpContext.fill();
                            if (!i) tmpContext.stroke();
                        }
                    } else if (obj.name == "pit trap") {
                        tmpContext.fillStyle = "#a5974c";
                        renderStar(tmpContext, 3, obj.scale * 1.1, obj.scale * 1.1);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = outlineColor;
                        renderStar(tmpContext, 3, obj.scale * 0.65, obj.scale * 0.65);
                        tmpContext.fill();
                    } else if (obj.name == "boost pad") {
                        tmpContext.fillStyle = "#7e7f82";
                        renderRect(0, 0, obj.scale*2, obj.scale*2, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#dbd97d";
                        renderTriangle(obj.scale * 1, tmpContext);
                    } else if (obj.name == "turret") {
                        tmpContext.fillStyle = "#a5974c";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#939393";
                        var tmpLen = 50;
                        renderRect(0, -tmpLen/2, obj.scale * 0.9, tmpLen, tmpContext);
                        renderCircle(0, 0, obj.scale * 0.6, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                    } else if (obj.name == "platform") {
                        tmpContext.fillStyle = "#cebd5f";
                        var tmpCount = 4;
                        var tmpS = obj.scale * 2;
                        var tmpW = tmpS / tmpCount;
                        var tmpX = -(obj.scale/2);
                        for (var i = 0; i < tmpCount; ++i) {
                            renderRect(tmpX - (tmpW/2), 0, tmpW, obj.scale*2, tmpContext);
                            tmpContext.fill();
                            tmpContext.stroke();
                            tmpX += tmpS / tmpCount;
                        }
                    } else if (obj.name == "healing pad") {
                        tmpContext.fillStyle = "#7e7f82";
                        renderRect(0, 0, obj.scale*2, obj.scale*2, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#db6e6e";
                        renderRectCircle(0, 0, obj.scale * 0.65, 20, 4, tmpContext, true);
                    } else if (obj.name == "spawn pad") {
                        tmpContext.fillStyle = "#7e7f82";
                        renderRect(0, 0, obj.scale*2, obj.scale*2, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.fillStyle = "#71aad6";
                        renderCircle(0, 0, obj.scale * 0.6, tmpContext);
                    } else if (obj.name == "blocker") {
                        tmpContext.fillStyle = "#7e7f82";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.rotate(Math.PI / 4);
                        tmpContext.fillStyle = "#db6e6e";
                        renderRectCircle(0, 0, obj.scale * 0.65, 20, 4, tmpContext, true);
                    }  else if (obj.name == "teleporter") {
                        tmpContext.fillStyle = "#7e7f82";
                        renderCircle(0, 0, obj.scale, tmpContext);
                        tmpContext.fill();
                        tmpContext.stroke();
                        tmpContext.rotate(Math.PI / 4);
                        tmpContext.fillStyle = "#d76edb";
                        renderCircle(0, 0, obj.scale * 0.5, tmpContext, true);
                    }
                    tmpSprite = tmpCanvas;
                    if (!asIcon)
                        itemSprites[obj.id] = tmpSprite;
                }
                return tmpSprite;
            }

            // RENDER LEAF:
            function renderLeaf(x, y, l, r, ctxt) {
                var endX = x + (l * Math.cos(r));
                var endY = y + (l * Math.sin(r));
                var width = l * 0.4;
                ctxt.moveTo(x, y);
                ctxt.beginPath();
                ctxt.quadraticCurveTo(((x + endX) / 2) + (width * Math.cos(r + Math.PI/2)),
                                      ((y + endY) / 2) + (width * Math.sin(r + Math.PI/2)), endX, endY);
                ctxt.quadraticCurveTo(((x + endX) / 2) - (width * Math.cos(r + Math.PI/2)),
                                      ((y + endY) / 2) - (width * Math.sin(r + Math.PI/2)), x, y);
                ctxt.closePath();
                ctxt.fill();
                ctxt.stroke();
            }

            // RENDER CIRCLE:
            function renderCircle(x, y, scale, tmpContext, dontStroke, dontFill) {
                tmpContext = tmpContext||mainContext;
                tmpContext.beginPath();
                tmpContext.arc(x, y, scale, 0, 2 * Math.PI);
                if (!dontFill) tmpContext.fill();
                if (!dontStroke) tmpContext.stroke();
            }

            // RENDER STAR SHAPE:
            function renderStar(ctxt, spikes, outer, inner) {
                var rot = Math.PI / 2 * 3;
                var x, y;
                var step = Math.PI / spikes;
                ctxt.beginPath();
                ctxt.moveTo(0, -outer);
                for (var i = 0; i < spikes; i++) {
                    x = Math.cos(rot) * outer;
                    y = Math.sin(rot) * outer;
                    ctxt.lineTo(x, y);
                    rot += step;
                    x = Math.cos(rot) * inner;
                    y = Math.sin(rot) * inner;
                    ctxt.lineTo(x, y);
                    rot += step;
                }
                ctxt.lineTo(0, -outer);
                ctxt.closePath();
            }

            // RENDER RECTANGLE:
            function renderRect(x, y, w, h, ctxt, stroke) {
                ctxt.fillRect(x - (w / 2), y - (h / 2), w, h);
                if (!stroke)
                    ctxt.strokeRect(x - (w / 2), y - (h / 2), w, h);
            }

            // RENDER RECTCIRCLE:
            function renderRectCircle(x, y, s, sw, seg, ctxt, stroke) {
                ctxt.save();
                ctxt.translate(x, y);
                seg = Math.ceil(seg / 2);
                for (var i = 0; i < seg; i++) {
                    renderRect(0, 0, s * 2, sw, ctxt, stroke);
                    ctxt.rotate(Math.PI / seg);
                }
                ctxt.restore();
            }

            // RENDER BLOB:
            function renderBlob(ctxt, spikes, outer, inner) {
                var rot = Math.PI / 2 * 3;
                var x, y;
                var step = Math.PI / spikes;
                var tmpOuter;
                ctxt.beginPath();
                ctxt.moveTo(0, -inner);
                for (var i = 0; i < spikes; i++) {
                    tmpOuter = UTILS.randInt(outer + 0.9, outer * 1.2);
                    ctxt.quadraticCurveTo(Math.cos(rot + step) * tmpOuter, Math.sin(rot + step) * tmpOuter,
                                          Math.cos(rot + (step * 2)) * inner, Math.sin(rot + (step * 2)) * inner);
                    rot += step * 2;
                }
                ctxt.lineTo(0, -inner);
                ctxt.closePath();
            }

            // RENDER TRIANGLE:
            function renderTriangle(s, ctx) {
                ctx = ctx||mainContext;
                var h = s * (Math.sqrt(3)/2);
                ctx.beginPath();
                ctx.moveTo(0, -h / 2);
                ctx.lineTo( -s / 2, h / 2);
                ctx.lineTo(s / 2, h / 2);
                ctx.lineTo(0, -h / 2);
                ctx.fill();
                ctx.closePath();
            }

            // PREPARE MENU BACKGROUND:
            function prepareMenuBackground() {}

            // LOAD GAME OBJECT:
            function loadGameObject(data) {
                for (var i = 0; i < data.length;) {
                    objectManager.add(data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4],
                                      data[i + 5], items.list[data[i + 6]], true, (data[i + 7]>=0?{sid:data[i + 7]}:null));
                    i+=8;
                }
            }

            // WIGGLE GAME OBJECT:
            function wiggleGameObject(dir, sid) {
                tmpObj = findObjectBySid(sid);
                if (tmpObj) {
                    tmpObj.xWiggle += config.gatherWiggle * Math.cos(dir);
                    tmpObj.yWiggle += config.gatherWiggle * Math.sin(dir);
                }
            }

            // SHOOT TURRET:
            function shootTurret(sid, dir) {
                tmpObj = findObjectBySid(sid);
                if (tmpObj) {
                    tmpObj.dir = dir;
                    tmpObj.xWiggle += config.gatherWiggle * Math.cos(dir+Math.PI);
                    tmpObj.yWiggle += config.gatherWiggle * Math.sin(dir+Math.PI);
                }
            }

            // ADD PROJECTILE:
            function addProjectile(x, y, dir, range, speed, indx, layer, sid, safe) {
                if(typeof safe !== "bigint") {
                    if(inWindow) projectileManager.addProjectile(x, y, dir, range, speed, indx, null, null, layer).sid = sid;

                    return storage.push({function: addProjectile, data: arguments});
                }


                const turret = Number(range == 700 && speed == 1.5);

                let index = 0, length = players.length;

                while(index < length) {
                    let player = players[index];

                    if(player.visible) {
                        const dist = Math.round(Math.hypot(player.y2 - y, player.x2 - x));
                        const Turret = (turret && player.reloads[2].ready && player.skinIndex === 53 && Number(dist) <= 5);
                        const Secondary = !turret && [9, 12, 13, 15].includes(player.weaponIndex) && !Number(player.d2 - dir) && [69, 70, 71, 72].includes(dist);

                        if(player.visible && (Secondary || Turret)) {
                            //send("game", `${player.name} shots ${player.weapon.name}.`);

                            if(turret){
                                player.reloads[2].date = Date.now();
                                player.reloads[2].count = 0;
                                player.reloads[2].ready = false;
                            } else {
                                player.reloads[1].date = Date.now();
                                player.reloads[1].count = 0;
                                player.reloads[1].ready = false;
                            }
                        }
                    }

                    index += 1;
                }
            }

            // REMOVE PROJECTILE:
            function remProjectile(sid, range) {
                for (var i = 0; i < projectiles.length; ++i) {
                    if (projectiles[i].sid == sid) {
                        projectiles[i].range = range;
                    }
                }
            }

            // ANIMATE AI:
            function animateAI(sid) {
                tmpObj = findAIBySID(sid);
                if (tmpObj) tmpObj.startAnim();
            }

            // ADD AI:
            function loadAI(data) {
                for (var i = 0; i < ais.length; ++i) {
                    ais[i].forcePos = !ais[i].visible;
                    ais[i].visible = false;
                }
                if (data) {
                    var tmpTime = Date.now();
                    for (var i = 0; i < data.length;) {
                        tmpObj = findAIBySID(data[i]);
                        if (tmpObj) {
                            tmpObj.index = data[i + 1];
                            tmpObj.t1 = (tmpObj.t2===undefined)?tmpTime:tmpObj.t2;
                            tmpObj.t2 = tmpTime;
                            tmpObj.x1 = tmpObj.x;
                            tmpObj.y1 = tmpObj.y;
                            tmpObj.x2 = data[i + 2];
                            tmpObj.y2 = data[i + 3];
                            tmpObj.d1 = (tmpObj.d2===undefined)?data[i + 4]:tmpObj.d2;
                            tmpObj.d2 = data[i + 4];
                            tmpObj.health = data[i + 5];
                            tmpObj.dt = 0;
                            tmpObj.visible = true;
                        } else {
                            tmpObj = aiManager.spawn(data[i + 2], data[i + 3], data[i + 4], data[i + 1]);
                            tmpObj.x2 = tmpObj.x;
                            tmpObj.y2 = tmpObj.y;
                            tmpObj.d2 = tmpObj.dir;
                            tmpObj.health = data[i + 5];
                            if (!aiManager.aiTypes[data[i + 1]].name)
                                tmpObj.name = config.cowNames[data[i + 6]];
                            tmpObj.forcePos = true;
                            tmpObj.sid = data[i];
                            tmpObj.visible = true;
                        }
                        i+=7;
                    }
                }
            }

            // RENDER AI:
            var aiSprites = {};
            function renderAI(obj, ctxt) {
                var tmpIndx = obj.index;
                var tmpSprite = aiSprites[tmpIndx];
                if (!tmpSprite) {
                    var tmpImg = new Image();
                    tmpImg.onload = function() {
                        this.isLoaded = true;
                        this.onload = null;
                    };
                    tmpImg.src = ".././img/animals/" + obj.src + ".png";
                    tmpSprite = tmpImg;
                    aiSprites[tmpIndx] = tmpSprite;
                }
                if (tmpSprite.isLoaded) {
                    var tmpScale = obj.scale * 1.2 * (obj.spriteMlt||1);
                    ctxt.drawImage(tmpSprite, -tmpScale, -tmpScale, tmpScale*2, tmpScale*2);
                }
            }

            // OBJECT ON SCREEN:
            function isOnScreen(x, y, s) {
                return (x + s >= 0 && x - s <= maxScreenWidth && y + s >= 0 && y - s <= maxScreenHeight)
            }

            // ADD NEW PLAYER:
            function addPlayer(data, isYou) {
                var tmpPlayer = findPlayerByID(data[0]);
                if (!tmpPlayer) {
                    tmpPlayer = new Player(data[0], data[1], config, UTILS, projectileManager,
                                           objectManager, players, ais, items, hats, accessories);
                    players.push(tmpPlayer);
                }
                tmpPlayer.spawn(isYou?moofoll:null);
                tmpPlayer.visible = false;
                tmpPlayer.x2 = undefined;
                tmpPlayer.y2 = undefined;
                tmpPlayer.setData(data);
                if (isYou) {
                    player = tmpPlayer;
                    camX = player.x;
                    camY = player.y;
                    updateItems();
                    updateStatusDisplay();
                    updateAge();
                    updateUpgrades(0);
                    gameUI.style.display = "block";
                }
            }

            // REMOVE PLAYER:
            function removePlayer(id) {
                for (var i = 0; i < players.length; i++) {
                    if (players[i].id == id) {
                        players.splice(i, 1);
                        break;
                    }
                }
            }

            // UPDATE PLAYER ITEM VALUES:
            function updateItemCounts(index, value) {
                if (player) {
                    player.itemCounts[index] = value;
                }
            }

            // UPDATE PLAYER VALUE:
            function updatePlayerValue(index, value, updateView) {
                if (player) {
                    player[index] = value;
                    if (updateView)
                        updateStatusDisplay();
                }
            }

            // UPDATE HEALTH:
            function updateHealth(sid, value) {
                tmpObj = findPlayerBySID(sid);
                if (tmpObj) {
                    tmpObj.health = value;
                }
            }

            // UPDATE PLAYER DATA:
            function updatePlayers(data) {
                var tmpTime = Date.now();
                for (var i = 0; i < players.length; ++i) {
                    players[i].forcePos = !players[i].visible;
                    players[i].visible = false;
                }
                for (var i = 0; i < data.length;) {
                    tmpObj = findPlayerBySID(data[i]);
                    if (tmpObj) {
                        tmpObj.t1 = (tmpObj.t2===undefined)?tmpTime:tmpObj.t2;
                        tmpObj.t2 = tmpTime;
                        tmpObj.x1 = tmpObj.x;
                        tmpObj.y1 = tmpObj.y;
                        // console.log("b", data[i + 1] - tmpObj.x2, data[i + 2] - tmpObj.y2)
                        tmpObj.x2 = data[i + 1];
                        tmpObj.y2 = data[i + 2];
                        tmpObj.d1 = (tmpObj.d2===undefined)?data[i + 3]:tmpObj.d2;
                        tmpObj.d2 = data[i + 3];
                        tmpObj.dt = 0;
                        tmpObj.buildIndex = data[i + 4];
                        tmpObj.weaponIndex = data[i + 5];
                        tmpObj.weaponVariant = data[i + 6];
                        tmpObj.team = data[i + 7];
                        tmpObj.isLeader = data[i + 8];
                        tmpObj.skinIndex = data[i + 9];
                        tmpObj.tailIndex = data[i + 10];
                        tmpObj.iconIndex = data[i + 11];
                        tmpObj.zIndex = data[i + 12];
                        tmpObj.visible = true;
                        tmpObj.skin = hats.find(hat => hat.id === tmpObj.skinIndex);
                        tmpObj.tail = accessories.find(acc => acc.id === tmpObj.tailIndex);
                    }
                    i+=13;
                }

                tick();
            }

            let storage = [];

            // UPDATE EVERY TICK
            function tick() {
                let index = 0, length = storage.length;

                while(index < length) {
                    storage[index] && storage[index].function(...storage[index].data, 2n);

                    index += 1;
                }

                storage = [];

                player.moveDir = getMoveDir();
                for(let player of players) {
                    let delta = Date.now() - (player.lastTouch || 0);

                    player.visible && player.update(delta);

                    player.lastTouch = Date.now()
                }
            }

            // FIND OBJECTS BY ID/SID:
            function findPlayerByID(id) {
                for (var i = 0; i < players.length; ++i) {
                    if (players[i].id == id) {
                        return players[i];
                    }
                } return null;
            }
            function findPlayerBySID(sid) {
                for (var i = 0; i < players.length; ++i) {
                    if (players[i].sid == sid) {
                        return players[i];
                    }
                } return null;
            }
            function findAIBySID(sid) {
                for (var i = 0; i < ais.length; ++i) {
                    if (ais[i].sid == sid) {
                        return ais[i];
                    }
                } return null;
            }
            function findObjectBySid(sid) {
                for (var i = 0; i < gameObjects.length; ++i) {
                    if (gameObjects[i].sid == sid) {
                        return gameObjects[i];
                    }
                } return null;
            }

            // PING:
            var lastPing = -1;
            function pingSocketResponse() {
                var pingTime = Date.now() - lastPing;
                window.pingTime = pingTime;
                // pingDisplay.innerText = "Ping: " + pingTime + " ms"
            }

            document.updateInfoBar = function() {
                pingDisplay.innerText = `PPS: [${io.pps.length}]\nPing: ${window.pingTime} ms`;
            }

            function pingSocket() {
                lastPing = Date.now();
                io.send("pp");
            }

            // SERVER SHUTDOWN NOTICE:
            function serverShutdownNotice(countdown) {
                if (countdown < 0) return;

                var minutes = Math.floor(countdown / 60);
                var seconds = countdown % 60;
                seconds = ("0" + seconds).slice(-2);

                shutdownDisplay.innerText = "Server restarting in " + minutes + ":" + seconds;
                shutdownDisplay.hidden = false;
            }

            // UPDATE & ANIMATE:
            window.requestAnimFrame = (function() {
                return window.requestAnimationFrame ||
                    window.webkitRequestAnimationFrame ||
                    window.mozRequestAnimationFrame ||
                    function(callback) {
                    window.setTimeout(callback, 1000 / 60);
                };
            })();
            function doUpdate() {
                now = Date.now();
                delta = now - lastUpdate;
                lastUpdate = now;
                updateGame();
                requestAnimFrame(doUpdate);
            }

            // START GAME:
            function startGame() {
                bindEvents();
                loadIcons();
                loadingText.style.display = "none";
                menuCardHolder.style.display = "block";
                nameInput.value = getSavedVal("moo_name")||"";
                prepareUI();
            }
            prepareMenuBackground();
            doUpdate();

            // OPEN LINK:
            function openLink(link) {
                window.open(link, "_blank")
            }

            // EXPORT VALUES:
            window.openLink = openLink;
            window.aJoinReq = aJoinReq;
            window.follmoo = follmoo;
            window.kickFromClan = kickFromClan;
            window.sendJoin = sendJoin;
            window.leaveAlliance = leaveAlliance;
            window.createAlliance = createAlliance;
            window.storeBuy = storeBuy;
            window.storeEquip = storeEquip;
            window.showItemInfo = showItemInfo;
            window.selectSkinColor = selectSkinColor;
            window.changeStoreIndex = changeStoreIndex;
            window.config = config;


            /***/ }),

        /***/ "./src/js/config.js":
        /*!**************************!*\
  !*** ./src/js/config.js ***!
  \**************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            /* WEBPACK VAR INJECTION */(function(process) {
                // RENDER:
                module.exports.maxScreenWidth = 1920;
                module.exports.maxScreenHeight = 1080;

                // SERVER:
                module.exports.serverUpdateRate = 9;
                module.exports.maxPlayers =  (process && process.argv.indexOf("--largeserver") != -1) ? 80 : 40;
                module.exports.maxPlayersHard =  module.exports.maxPlayers + 10;
                module.exports.collisionDepth = 6;
                module.exports.minimapRate = 3000;

                // COLLISIONS:
                module.exports.colGrid = 10;

                // CLIENT:
                module.exports.clientSendRate = 5;

                // UI:
                module.exports.healthBarWidth = 50;
                module.exports.healthBarPad = 4.5;
                module.exports.iconPadding = 15;
                module.exports.iconPad = 0.9;
                module.exports.deathFadeout = 3000;
                module.exports.crownIconScale = 60;
                module.exports.crownPad = 35;

                // CHAT:
                module.exports.chatCountdown = 3000;
                module.exports.chatCooldown = 500;

                // SANDBOX:
                module.exports.inSandbox = process && process.env.VULTR_SCHEME === "mm_exp";;

                // PLAYER:
                module.exports.maxAge = 100;
                module.exports.gatherAngle = Math.PI/2.6;
                module.exports.gatherWiggle = 10;
                module.exports.hitReturnRatio = 0.25;
                module.exports.hitAngle = Math.PI / 2;
                module.exports.playerScale = 35;
                module.exports.playerSpeed = 0.0016;
                module.exports.playerDecel = 0.993;
                module.exports.nameY = 34;

                // CUSTOMIZATION:
                module.exports.skinColors = ["#bf8f54", "#cbb091", "#896c4b",
                                             "#fadadc", "#ececec", "#c37373", "#4c4c4c", "#ecaff7", "#738cc3",
                                             "#8bc373"];

                // ANIMALS:
                module.exports.animalCount = 7;
                module.exports.aiTurnRandom = 0.06;
                module.exports.cowNames = ["Sid", "Steph", "Bmoe", "Romn", "Jononthecool", "Fiona", "Vince", "Nathan", "Nick", "Flappy", "Ronald", "Otis", "Pepe", "Mc Donald", "Theo", "Fabz", "Oliver", "Jeff", "Jimmy", "Helena", "Reaper",
                                           "Ben", "Alan", "Naomi", "XYZ", "Clever", "Jeremy", "Mike", "Destined", "Stallion", "Allison", "Meaty", "Sophia", "Vaja", "Joey", "Pendy", "Murdoch", "Theo", "Jared", "July", "Sonia", "Mel", "Dexter", "Quinn", "Milky"];

                // WEAPONS:
                module.exports.shieldAngle = Math.PI/3;
                module.exports.weaponVariants = [{
                    id: 0,
                    src: "",
                    xp: 0,
                    val: 1
                }, {
                    id: 1,
                    src: "_g",
                    xp: 3000,
                    val: 1.1
                }, {
                    id: 2,
                    src: "_d",
                    xp: 7000,
                    val: 1.18
                }, {
                    id: 3,
                    src: "_r",
                    poison: true,
                    xp: 12000,
                    val: 1.18
                }];
                module.exports.fetchVariant = function(player) {
                    var tmpXP = player.weaponXP[player.weaponIndex]||0;
                    for (var i = module.exports.weaponVariants.length - 1; i >= 0; --i) {
                        if (tmpXP >= module.exports.weaponVariants[i].xp)
                            return module.exports.weaponVariants[i];
                    }
                };

                // NATURE:
                module.exports.resourceTypes = ["wood", "food", "stone", "points"];
                module.exports.areaCount = 7;
                module.exports.treesPerArea = 9;
                module.exports.bushesPerArea = 3;
                module.exports.totalRocks = 32;
                module.exports.goldOres = 7;
                module.exports.riverWidth = 724;
                module.exports.riverPadding = 114;
                module.exports.waterCurrent = 0.0011;
                module.exports.waveSpeed = 0.0001;
                module.exports.waveMax = 1.3;
                module.exports.treeScales = [150, 160, 165, 175];
                module.exports.bushScales = [80, 85, 95];
                module.exports.rockScales = [80, 85, 90];

                // BIOME DATA:
                module.exports.snowBiomeTop = 2400;
                module.exports.snowSpeed = 0.75;

                // DATA:
                module.exports.maxNameLength = 15;

                // MAP:
                module.exports.mapScale = 14400;
                module.exports.mapPingScale = 40;
                module.exports.mapPingTime = 2200;

                // VOLCANO:
                module.exports.volcanoScale = 100;
                module.exports.volcano = {
                    x: 14400 - 440,
                    y: 14400 - 440
                };

                /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../node_modules/process/browser.js */ "./node_modules/process/browser.js")))

            /***/ }),

        /***/ "./src/js/data/ai.js":
        /*!***************************!*\
  !*** ./src/js/data/ai.js ***!
  \***************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            var PI2 = Math.PI * 2;
            module.exports = function(sid, objectManager, players, items, UTILS, config, scoreCallback, server) {
                this.sid = sid;
                this.isAI = true;
                this.nameIndex = UTILS.randInt(0, config.cowNames.length-1);

                // INIT:
                this.init = function(x, y, dir, index, data) {
                    this.x = x;
                    this.y = y;
                    this.startX = data.fixedSpawn?x:null;
                    this.startY = data.fixedSpawn?y:null;
                    this.xVel = 0;
                    this.yVel = 0;
                    this.zIndex = 0;
                    this.dir = dir;
                    this.dirPlus = 0;
                    this.index = index;
                    this.src = data.src;
                    if (data.name) this.name = data.name;
                    this.weightM = data.weightM;
                    this.speed = data.speed;
                    this.killScore = data.killScore;
                    this.turnSpeed = data.turnSpeed;
                    this.scale = data.scale;
                    this.maxHealth = data.health;
                    this.leapForce = data.leapForce;
                    this.health = this.maxHealth;
                    this.chargePlayer = data.chargePlayer;
                    this.viewRange = data.viewRange;
                    this.drop = data.drop;
                    this.dmg = data.dmg;
                    this.hostile = data.hostile;
                    this.dontRun = data.dontRun;
                    this.hitRange = data.hitRange;
                    this.hitDelay = data.hitDelay;
                    this.hitScare = data.hitScare;
                    this.spriteMlt = data.spriteMlt;
                    this.nameScale = data.nameScale;
                    this.colDmg = data.colDmg;
                    this.noTrap = data.noTrap;
                    this.spawnDelay = data.spawnDelay;
                    this.hitWait = 0;
                    this.waitCount = 1000;
                    this.moveCount = 0;
                    this.targetDir = 0;
                    this.active = true;
                    this.alive = true;
                    this.runFrom = null;
                    this.chargeTarget = null;
                    this.dmgOverTime = {};
                };

                // UPDATE:
                var timerCount = 0;
                this.update = function(delta) {
                    if (this.active) {

                        // SPAWN DELAY:
                        if (this.spawnCounter) {
                            this.spawnCounter -= delta;
                            if (this.spawnCounter <= 0) {
                                this.spawnCounter = 0;
                                this.x = this.startX||UTILS.randInt(0, config.mapScale);
                                this.y = this.startY||UTILS.randInt(0, config.mapScale);
                            }
                            return;
                        }

                        // REGENS AND AUTO:
                        timerCount -= delta;
                        if (timerCount <= 0) {
                            if (this.dmgOverTime.dmg) {
                                //  this.changeHealth(-this.dmgOverTime.dmg, this.dmgOverTime.doer);
                                this.dmgOverTime.time -= 1;
                                if (this.dmgOverTime.time <= 0)
                                    this.dmgOverTime.dmg = 0;
                            }
                            timerCount = 1000;
                        }

                        // BEHAVIOUR:
                        var charging = false;
                        var slowMlt = 1;
                        if (!this.zIndex && !this.lockMove && this.y >= (config.mapScale / 2) - (config.riverWidth / 2) &&
                            this.y <= (config.mapScale / 2) + (config.riverWidth / 2)) {
                            slowMlt = 0.33;
                            this.xVel += config.waterCurrent * delta;
                        }
                        if (this.lockMove) {
                            this.xVel = 0;
                            this.yVel = 0;
                        } else if (this.waitCount > 0) {
                            this.waitCount -= delta;
                            if (this.waitCount <= 0) {
                                if (this.chargePlayer) {
                                    var tmpPlayer, bestDst, tmpDist;
                                    for (var i = 0; i < players.length; ++i) {
                                        if (players[i].visible && !(players[i].skin && players[i].skin.bullRepel)) {
                                            tmpDist = UTILS.getDistance(this.x, this.y, players[i].x, players[i].y);
                                            if (tmpDist <= this.viewRange && (!tmpPlayer || tmpDist < bestDst)) {
                                                bestDst = tmpDist;
                                                tmpPlayer = players[i];
                                            }
                                        }
                                    }
                                    if (tmpPlayer) {
                                        this.chargeTarget = tmpPlayer;
                                        this.moveCount = UTILS.randInt(8000, 12000);
                                    } else {
                                        this.moveCount = UTILS.randInt(1000, 2000);
                                        this.targetDir = UTILS.randFloat(-Math.PI, Math.PI);
                                    }
                                } else {
                                    this.moveCount = UTILS.randInt(4000, 10000);
                                    this.targetDir = UTILS.randFloat(-Math.PI, Math.PI);
                                }
                            }
                        } else if (this.moveCount > 0) {
                            var tmpSpd = this.speed * slowMlt;
                            if (this.runFrom && this.runFrom.active && !(this.runFrom.isPlayer && !this.runFrom.alive)) {
                                this.targetDir = UTILS.getDirection(this.x, this.y, this.runFrom.x, this.runFrom.y);
                                tmpSpd *= 1.42;
                            } else if (this.chargeTarget && this.chargeTarget.alive) {
                                this.targetDir = UTILS.getDirection(this.chargeTarget.x, this.chargeTarget.y, this.x, this.y);
                                tmpSpd *= 1.75;
                                charging = true;
                            } if (this.hitWait) {
                                tmpSpd *= 0.3;
                            }
                            if (this.dir != this.targetDir) {
                                this.dir %= PI2;
                                var netAngle = (this.dir - this.targetDir + PI2) % PI2;
                                var amnt = Math.min(Math.abs(netAngle - PI2), netAngle, this.turnSpeed * delta);
                                var sign = (netAngle - Math.PI)>=0?1:-1;
                                this.dir += sign * amnt + PI2;
                            }
                            this.dir %= PI2;
                            this.xVel += (tmpSpd * delta) * Math.cos(this.dir);
                            this.yVel += (tmpSpd * delta) * Math.sin(this.dir);
                            this.moveCount -= delta;
                            if (this.moveCount <= 0) {
                                this.runFrom = null;
                                this.chargeTarget = null;
                                this.waitCount = this.hostile?1500:UTILS.randInt(1500, 6000);
                            }
                        }

                        // OBJECT COLL:
                        this.zIndex = 0;
                        this.lockMove = false;
                        var tmpList;
                        var tmpSpeed = UTILS.getDistance(0, 0, this.xVel * delta, this.yVel * delta);
                        var depth = Math.min(4, Math.max(1, Math.round(tmpSpeed / 40)));
                        var tMlt = 1 / depth;
                        for (var i = 0; i < depth; ++i) {
                            if (this.xVel)
                                this.x += (this.xVel * delta) * tMlt;
                            if (this.yVel)
                                this.y += (this.yVel * delta) * tMlt;
                            tmpList = objectManager.getGridArrays(this.x, this.y, this.scale);
                            for (var x = 0; x < tmpList.length; ++x) {
                                for (var y = 0; y < tmpList[x].length; ++y) {
                                    if (tmpList[x][y].active)
                                        objectManager.checkCollision(this, tmpList[x][y], tMlt);
                                }
                            }
                        }

                        // HITTING:
                        var hitting = false;
                        if (this.hitWait > 0) {
                            this.hitWait -= delta;
                            if (this.hitWait <= 0) {
                                hitting = true;
                                this.hitWait = 0;
                                if (this.leapForce && !UTILS.randInt(0, 2)) {
                                    this.xVel += this.leapForce * Math.cos(this.dir);
                                    this.yVel += this.leapForce * Math.sin(this.dir);
                                }
                                var tmpList = objectManager.getGridArrays(this.x, this.y, this.hitRange);
                                var tmpObj, tmpDst;
                                for (var t = 0; t < tmpList.length; ++t) {
                                    for (var x = 0; x < tmpList[t].length; ++x) {
                                        tmpObj = tmpList[t][x];
                                        if (tmpObj.health) {
                                            tmpDst = UTILS.getDistance(this.x, this.y, tmpObj.x, tmpObj.y);
                                            if (tmpDst < tmpObj.scale + this.hitRange) {
                                                if (tmpObj.changeHealth(-this.dmg * 5) && false) objectManager.disableObj(tmpObj);
                                                objectManager.hitObj(tmpObj, UTILS.getDirection(this.x, this.y, tmpObj.x, tmpObj.y));
                                            }
                                        }
                                    }
                                }
                                for (var x = 0; x < players.length; ++x) {
                                    if (players[x].canSee(this)) {
                                        server.send(players[x].id, "aa", this.sid);
                                    }
                                }
                            }
                        }

                        // PLAYER COLLISIONS:
                        if (charging || hitting) {
                            var tmpObj, tmpDst, tmpDir;
                            for (var i = 0; i < players.length; ++i) {
                                tmpObj = players[i];
                                if (tmpObj && tmpObj.alive) {
                                    tmpDst = UTILS.getDistance(this.x, this.y, tmpObj.x, tmpObj.y);
                                    if (this.hitRange) {
                                        if  (!this.hitWait && tmpDst <= this.hitRange + tmpObj.scale) {
                                            if (hitting) {
                                                tmpDir = UTILS.getDirection(tmpObj.x, tmpObj.y, this.x, this.y);
                                                tmpObj.changeHealth(-this.dmg);
                                                tmpObj.xVel += 0.6 * Math.cos(tmpDir);
                                                tmpObj.yVel += 0.6 * Math.sin(tmpDir);
                                                this.runFrom = null;
                                                this.chargeTarget = null;
                                                this.waitCount = 3000;
                                                this.hitWait = (!UTILS.randInt(0, 2)?600:0);
                                            } else this.hitWait = this.hitDelay;
                                        }
                                    } else if (tmpDst <= this.scale + tmpObj.scale) {
                                        tmpDir = UTILS.getDirection(tmpObj.x, tmpObj.y, this.x, this.y);
                                        tmpObj.changeHealth(-this.dmg);
                                        tmpObj.xVel += 0.55 * Math.cos(tmpDir);
                                        tmpObj.yVel += 0.55 * Math.sin(tmpDir);
                                    }
                                }
                            }
                        }

                        // DECEL:
                        if (this.xVel)
                            this.xVel *= Math.pow(config.playerDecel, delta);
                        if (this.yVel)
                            this.yVel *= Math.pow(config.playerDecel, delta);

                        // MAP BOUNDARIES:
                        var tmpScale = this.scale;
                        if (this.x - tmpScale < 0) {
                            this.x = tmpScale;
                            this.xVel = 0;
                        } else if (this.x + tmpScale > config.mapScale) {
                            this.x = config.mapScale - tmpScale;
                            this.xVel = 0;
                        } if (this.y - tmpScale < 0) {
                            this.y = tmpScale;
                            this.yVel = 0;
                        } else if (this.y + tmpScale > config.mapScale) {
                            this.y = config.mapScale - tmpScale;
                            this.yVel = 0;
                        }

                    }
                };

                // CAN SEE:
                this.canSee = function(other) {
                    if (!other) return false;
                    if (other.skin && other.skin.invisTimer && other.noMovTimer
                        >= other.skin.invisTimer) return false;
                    var dx = Math.abs(other.x - this.x) - other.scale;
                    var dy = Math.abs(other.y - this.y) - other.scale;
                    return dx <= (config.maxScreenWidth / 2) * 1.3 && dy <= (config.maxScreenHeight / 2) * 1.3;
                };

                var tmpRatio = 0;
                var animIndex = 0;
                this.animate = function(delta) {
                    if (this.animTime > 0) {
                        this.animTime -= delta;
                        if (this.animTime <= 0) {
                            this.animTime = 0;
                            this.dirPlus = 0;
                            tmpRatio = 0;
                            animIndex = 0;
                        } else {
                            if (animIndex == 0) {
                                tmpRatio += delta / (this.animSpeed * config.hitReturnRatio);
                                this.dirPlus = UTILS.lerp(0, this.targetAngle, Math.min(1, tmpRatio));
                                if (tmpRatio >= 1) {
                                    tmpRatio = 1;
                                    animIndex = 1;
                                }
                            } else {
                                tmpRatio -= delta / (this.animSpeed * (1-config.hitReturnRatio));
                                this.dirPlus = UTILS.lerp(0, this.targetAngle, Math.max(0, tmpRatio));
                            }
                        }
                    }
                };

                // ANIMATION:
                this.startAnim = function() {
                    this.animTime = this.animSpeed = 600;
                    this.targetAngle = Math.PI * 0.8;
                    tmpRatio = 0;
                    animIndex = 0;
                };

                // CHANGE HEALTH:
                this.changeHealth = function(val, doer, runFrom) {
                    return
                    if (this.active) {
                        this.health += val;
                        if (runFrom) {
                            if (this.hitScare && !UTILS.randInt(0, this.hitScare)) {
                                this.runFrom = runFrom;
                                this.waitCount = 0;
                                this.moveCount = 2000;
                            } else if (this.hostile && this.chargePlayer && runFrom.isPlayer) {
                                this.chargeTarget = runFrom;
                                this.waitCount = 0;
                                this.moveCount = 8000;
                            } else if (!this.dontRun) {
                                this.runFrom = runFrom;
                                this.waitCount = 0;
                                this.moveCount = 2000;
                            }
                        }
                        if (val < 0 && this.hitRange && UTILS.randInt(0, 1)) this.hitWait = 500;
                        if (doer && doer.canSee(this) && val < 0) {
                            server.send(doer.id, "t", Math.round(this.x),
                                        Math.round(this.y), Math.round(-val), 1);
                        } if (this.health <= 0) {
                            if (this.spawnDelay) {
                                this.spawnCounter = this.spawnDelay;
                                this.x = -1000000;
                                this.y = -1000000;
                            } else {
                                this.x = this.startX||UTILS.randInt(0, config.mapScale);
                                this.y = this.startY||UTILS.randInt(0, config.mapScale);
                            }
                            this.health = this.maxHealth;
                            this.runFrom = null;
                            if (doer) {
                                scoreCallback(doer, this.killScore);
                                if (this.drop) {
                                    for (var i = 0; i < this.drop.length;) {
                                        doer.addResource(config.resourceTypes.indexOf(this.drop[i]), this.drop[i+1]);
                                        i+=2;
                                    }
                                }
                            }
                        }
                    }
                };

            };


            /***/ }),

        /***/ "./src/js/data/aiManager.js":
        /*!**********************************!*\
  !*** ./src/js/data/aiManager.js ***!
  \**********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // AI MANAGER:
            module.exports = function(ais, AI, players, items, objectManager, config, UTILS, scoreCallback, server) {

                // AI TYPES:
                this.aiTypes = [{
                    id: 0,
                    src: "cow_1",
                    killScore: 150,
                    health: 500,
                    weightM: 0.8,
                    speed: 0.00095,
                    turnSpeed: 0.001,
                    scale: 72,
                    drop: ["food", 50]
                }, {
                    id: 1,
                    src: "pig_1",
                    killScore: 200,
                    health: 800,
                    weightM: 0.6,
                    speed: 0.00085,
                    turnSpeed: 0.001,
                    scale: 72,
                    drop: ["food", 80]
                }, {
                    id: 2,
                    name: "Bull",
                    src: "bull_2",
                    hostile: true,
                    dmg: 20,
                    killScore: 1000,
                    health: 1800,
                    weightM: 0.5,
                    speed: 0.00094,
                    turnSpeed: 0.00074,
                    scale: 78,
                    viewRange: 800,
                    chargePlayer: true,
                    drop: ["food", 100]
                }, {
                    id: 3,
                    name: "Bully",
                    src: "bull_1",
                    hostile: true,
                    dmg: 20,
                    killScore: 2000,
                    health: 2800,
                    weightM: 0.45,
                    speed: 0.001,
                    turnSpeed: 0.0008,
                    scale: 90,
                    viewRange: 900,
                    chargePlayer: true,
                    drop: ["food", 400]
                }, {
                    id: 4,
                    name: "Wolf",
                    src: "wolf_1",
                    hostile: true,
                    dmg: 8,
                    killScore: 500,
                    health: 300,
                    weightM: 0.45,
                    speed: 0.001,
                    turnSpeed: 0.002,
                    scale: 84,
                    viewRange: 800,
                    chargePlayer: true,
                    drop: ["food", 200]
                }, {
                    id: 5,
                    name: "Quack",
                    src: "chicken_1",
                    dmg: 8,
                    killScore: 2000,
                    noTrap: true,
                    health: 300,
                    weightM: 0.2,
                    speed: 0.0018,
                    turnSpeed: 0.006,
                    scale: 70,
                    drop: ["food", 100]
                }, {
                    id: 6,
                    name: "MOOSTAFA",
                    nameScale: 50,
                    src: "enemy",
                    hostile: true,
                    dontRun: true,
                    fixedSpawn: true,
                    spawnDelay: 60000,
                    noTrap: true,
                    colDmg: 100,
                    dmg: 40,
                    killScore: 8000,
                    health: 18000,
                    weightM: 0.4,
                    speed: 0.0007,
                    turnSpeed: 0.01,
                    scale: 80,
                    spriteMlt: 1.8,
                    leapForce: 0.9,
                    viewRange: 1000,
                    hitRange: 210,
                    hitDelay: 1000,
                    chargePlayer: true,
                    drop: ["food", 100]
                }, {
                    id: 7,
                    name: "Treasure",
                    hostile: true,
                    nameScale: 35,
                    src: "crate_1",
                    fixedSpawn: true,
                    spawnDelay: 120000,
                    colDmg: 200,
                    killScore: 5000,
                    health: 20000,
                    weightM: 0.1,
                    speed: 0.0,
                    turnSpeed: 0.0,
                    scale: 70,
                    spriteMlt: 1.0
                }, {
                    id: 8,
                    name: "MOOFIE",
                    src: "wolf_2",
                    hostile: true,
                    fixedSpawn: true,
                    dontRun: true,
                    hitScare: 4,
                    spawnDelay: 30000,
                    noTrap: true,
                    nameScale: 35,
                    dmg: 10,
                    colDmg: 100,
                    killScore: 3000,
                    health: 7000,
                    weightM: 0.45,
                    speed: 0.0015,
                    turnSpeed: 0.002,
                    scale: 90,
                    viewRange: 800,
                    chargePlayer: true,
                    drop: ["food", 1000]
                },
                                {
                                    id: 9,
                                    name: "MOOFIE",
                                    src: "wolf_2",
                                    hostile: !0,
                                    fixedSpawn: !0,
                                    dontRun: !0,
                                    hitScare: 50,
                                    spawnDelay: 6e4,
                                    noTrap: !0,
                                    nameScale: 35,
                                    dmg: 12,
                                    colDmg: 100,
                                    killScore: 3e3,
                                    health: 9e3,
                                    weightM: 0.45,
                                    speed: 0.0015,
                                    turnSpeed: 0.0025,
                                    scale: 94,
                                    viewRange: 1440,
                                    chargePlayer: !0,
                                    drop: ["food", 3e3],
                                    minSpawnRange: 0.85,
                                    maxSpawnRange: 0.9,
                                },
                                {
                                    id: 10,
                                    name: "Wolf",
                                    src: "wolf_1",
                                    hostile: !0,
                                    fixedSpawn: !0,
                                    dontRun: !0,
                                    hitScare: 50,
                                    spawnDelay: 3e4,
                                    dmg: 10,
                                    killScore: 700,
                                    health: 500,
                                    weightM: 0.45,
                                    speed: 0.00115,
                                    turnSpeed: 0.0025,
                                    scale: 88,
                                    viewRange: 1440,
                                    chargePlayer: !0,
                                    drop: ["food", 400],
                                    minSpawnRange: 0.85,
                                    maxSpawnRange: 0.9,
                                },
                                {
                                    id: 11,
                                    name: "Bully",
                                    src: "bull_1",
                                    hostile: !0,
                                    fixedSpawn: !0,
                                    dontRun: !0,
                                    hitScare: 50,
                                    dmg: 20,
                                    killScore: 5e3,
                                    health: 5e3,
                                    spawnDelay: 1e5,
                                    weightM: 0.45,
                                    speed: 0.00115,
                                    turnSpeed: 0.0025,
                                    scale: 94,
                                    viewRange: 1440,
                                    chargePlayer: !0,
                                    drop: ["food", 800],
                                    minSpawnRange: 0.85,
                                    maxSpawnRange: 0.9,
                                },
                               ];

                // SPAWN AI:
                this.spawn = function(x, y, dir, index) {
                    var tmpObj;
                    for (var i = 0; i < ais.length; ++i) {
                        if (!ais[i].active) {
                            tmpObj = ais[i];
                            break;
                        }
                    }
                    if (!tmpObj) {
                        tmpObj = new AI(ais.length, objectManager, players, items, UTILS, config, scoreCallback, server);
                        ais.push(tmpObj);
                    }

                    tmpObj.init(x, y, dir, index, this.aiTypes[index]);
                    return tmpObj;
                };

            };


            /***/ }),

        /***/ "./src/js/data/gameObject.js":
        /*!***********************************!*\
  !*** ./src/js/data/gameObject.js ***!
  \***********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            module.exports = function (sid) {
                this.sid = sid;

                // INIT:
                this.init = function(x, y, dir, scale, type, data, owner) {
                    data = data||{};
                    this.sentTo = {};
                    this.gridLocations = [];
                    this.active = true;
                    this.doUpdate = data.doUpdate;
                    this.x = x;
                    this.y = y;
                    this.dir = dir;
                    this.xWiggle = 0;
                    this.yWiggle = 0;
                    this.scale = scale;
                    this.type = type;
                    this.id = data.id;
                    this.owner = owner;
                    this.name = data.name;
                    this.isItem = (this.id!=undefined);
                    this.group = data.group;
                    this.health = data.health;
                    this.maxHealth = data.health;
                    this.layer = 2;
                    if (this.group != undefined) {
                        this.layer = this.group.layer;
                    } else if (this.type == 0) {
                        this.layer = 3;
                    } else if (this.type == 2) {
                        this.layer = 0;
                    }  else if (this.type == 4) {
                        this.layer = -1;
                    }
                    this.colDiv = data.colDiv||1;
                    this.blocker = data.blocker;
                    this.ignoreCollision = data.ignoreCollision;
                    this.dontGather = data.dontGather;
                    this.hideFromEnemy = data.hideFromEnemy;
                    this.friction = data.friction;
                    this.projDmg = data.projDmg;
                    this.dmg = data.dmg;
                    this.pDmg = data.pDmg;
                    this.pps = data.pps;
                    this.zIndex = data.zIndex||0;
                    this.turnSpeed = data.turnSpeed;
                    this.req = data.req;
                    this.trap = data.trap;
                    this.healCol = data.healCol;
                    this.teleport = data.teleport;
                    this.boostSpeed = data.boostSpeed;
                    this.projectile = data.projectile;
                    this.shootRange = data.shootRange;
                    this.shootRate = data.shootRate;
                    this.shootCount = this.shootRate;
                    this.spawnPoint = data.spawnPoint;
                };

                // GET HIT:
                this.changeHealth = function(amount, doer) {
                    this.health += amount;
                    return (this.health <= 0);
                };

                // GET SCALE:
                this.getScale = function(sM, ig) {
                    sM = sM||1;
                    return this.scale * ((this.isItem||this.type==2||this.type==3||this.type==4)
                                         ?1:(0.6*sM)) * (ig?1:this.colDiv);
                };

                // VISIBLE TO PLAYER:
                this.visibleToPlayer = function(player) {
                    return !(this.hideFromEnemy) || (this.owner && (this.owner == player ||
                                                                    (this.owner.team && player.team == this.owner.team)));
                };

                // UPDATE:
                this.update = function(delta) {
                    if (this.active) {
                        if (this.xWiggle) {
                            this.xWiggle *= Math.pow(0.99, delta);
                        } if (this.yWiggle) {
                            this.yWiggle *= Math.pow(0.99, delta);
                        }
                        if (this.turnSpeed) {
                            this.dir += this.turnSpeed * delta;
                        }
                    }
                };
            };


            /***/ }),

        /***/ "./src/js/data/items.js":
        /*!******************************!*\
  !*** ./src/js/data/items.js ***!
  \******************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // ITEM GROUPS:
            module.exports.groups = [{
                id: 0,
                name: "food",
                layer: 0
            }, {
                id: 1,
                name: "walls",
                place: true,
                limit: 30,
                layer: 0
            }, {
                id: 2,
                name: "spikes",
                place: true,
                limit: 15,
                layer: 0
            }, {
                id: 3,
                name: "mill",
                place: true,
                limit: 7,
                layer: 1
            }, {
                id: 4,
                name: "mine",
                place: true,
                limit: 1,
                layer: 0
            }, {
                id: 5,
                name: "trap",
                place: true,
                limit: 6,
                layer: -1
            }, {
                id: 6,
                name: "booster",
                place: true,
                limit: 12,
                layer: -1
            }, {
                id: 7,
                name: "turret",
                place: true,
                limit: 2,
                layer: 1
            }, {
                id: 8,
                name: "watchtower",
                place: true,
                limit: 12,
                layer: 1
            }, {
                id: 9,
                name: "buff",
                place: true,
                limit: 4,
                layer: -1
            }, {
                id: 10,
                name: "spawn",
                place: true,
                limit: 1,
                layer: -1
            }, {
                id: 11,
                name: "sapling",
                place: true,
                limit: 2,
                layer: 0
            }, {
                id: 12,
                name: "blocker",
                place: true,
                limit: 3,
                layer: -1
            }, {
                id: 13,
                name: "teleporter",
                place: true,
                limit: 2,
                layer: -1
            }];

            // PROJECTILES:
            exports.projectiles = [{
                indx: 0,
                layer: 0,
                src: "arrow_1",
                dmg: 25,
                speed: 1.6,
                scale: 103,
                range: 1000
            }, {
                indx: 1,
                layer: 1,
                dmg: 25,
                scale: 20
            }, {
                indx: 0,
                layer: 0,
                src: "arrow_1",
                dmg: 35,
                speed: 2.5,
                scale: 103,
                range: 1200
            }, {
                indx: 0,
                layer: 0,
                src: "arrow_1",
                dmg: 30,
                speed: 2,
                scale: 103,
                range: 1200
            }, {
                indx: 1,
                layer: 1,
                dmg: 16,
                scale: 20
            }, {
                indx: 0,
                layer: 0,
                src: "bullet_1",
                dmg: 50,
                speed: 3.6,
                scale: 160,
                range: 1400
            }];

            // WEAPONS:
            exports.weapons = [{
                id: 0,
                type: 0,
                name: "tool hammer",
                desc: "tool for gathering all resources",
                src: "hammer_1",
                length: 140,
                width: 140,
                xOff: -3,
                yOff: 18,
                dmg: 25,
                range: 65,
                gather: 1,
                speed: 300
            }, {
                id: 1,
                type: 0,
                age: 2,
                name: "hand axe",
                desc: "gathers resources at a higher rate",
                src: "axe_1",
                length: 140,
                width: 140,
                xOff: 3,
                yOff: 24,
                dmg: 30,
                spdMult: 1,
                range: 70,
                gather: 2,
                speed: 400
            }, {
                id: 2,
                type: 0,
                age: 8,
                pre: 1,
                name: "great axe",
                desc: "deal more damage and gather more resources",
                src: "great_axe_1",
                length: 140,
                width: 140,
                xOff: -8,
                yOff: 25,
                dmg: 35,
                spdMult: 1,
                range: 75,
                gather: 4,
                speed: 400
            }, {
                id: 3,
                type: 0,
                age: 2,
                name: "short sword",
                desc: "increased attack power but slower move speed",
                src: "sword_1",
                iPad: 1.3,
                length: 130,
                width: 210,
                xOff: -8,
                yOff: 46,
                dmg: 35,
                spdMult: 0.85,
                range: 110,
                gather: 1,
                speed: 300
            }, {
                id: 4,
                type: 0,
                age: 8,
                pre: 3,
                name: "katana",
                desc: "greater range and damage",
                src: "samurai_1",
                iPad: 1.3,
                length: 130,
                width: 210,
                xOff: -8,
                yOff: 59,
                dmg: 40,
                spdMult: 0.8,
                range: 118,
                gather: 1,
                speed: 300
            }, {
                id: 5,
                type: 0,
                age: 2,
                name: "polearm",
                desc: "long range melee weapon",
                src: "spear_1",
                iPad: 1.3,
                length: 130,
                width: 210,
                xOff: -8,
                yOff: 53,
                dmg: 45,
                knock: 0.2,
                spdMult: 0.82,
                range: 142,
                gather: 1,
                speed: 700
            }, {
                id: 6,
                type: 0,
                age: 2,
                name: "bat",
                desc: "fast long range melee weapon",
                src: "bat_1",
                iPad: 1.3,
                length: 110,
                width: 180,
                xOff: -8,
                yOff: 53,
                dmg: 20,
                knock: 0.7,
                range: 110,
                gather: 1,
                speed: 300
            }, {
                id: 7,
                type: 0,
                age: 2,
                name: "daggers",
                desc: "really fast short range weapon",
                src: "dagger_1",
                iPad: 0.8,
                length: 110,
                width: 110,
                xOff: 18,
                yOff: 0,
                dmg: 20,
                knock: 0.1,
                range: 65,
                gather: 1,
                hitSlow: 0.1,
                spdMult: 1.13,
                speed: 100
            }, {
                id: 8,
                type: 0,
                age: 2,
                name: "stick",
                desc: "great for gathering but very weak",
                src: "stick_1",
                length: 140,
                width: 140,
                xOff: 3,
                yOff: 24,
                dmg: 1,
                spdMult: 1,
                range: 70,
                gather: 7,
                speed: 400
            }, {
                id: 9,
                type: 1,
                age: 6,
                name: "hunting bow",
                desc: "bow used for ranged combat and hunting",
                src: "bow_1",
                req: ["wood", 4],
                length: 120,
                width: 120,
                xOff: -6,
                yOff: 0,
                projectile: 0,
                spdMult: 0.75,
                speed: 600
            }, {
                id: 10,
                type: 1,
                age: 6,
                name: "great hammer",
                desc: "hammer used for destroying structures",
                src: "great_hammer_1",
                length: 140,
                width: 140,
                xOff: -9,
                yOff: 25,
                dmg: 10,
                spdMult: 0.88,
                range: 75,
                sDmg: 7.5,
                gather: 1,
                speed: 400
            }, {
                id: 11,
                type: 1,
                age: 6,
                name: "wooden shield",
                desc: "blocks projectiles and reduces melee damage",
                src: "shield_1",
                length: 120,
                width: 120,
                shield: 0.2,
                xOff: 6,
                yOff: 0,
                spdMult: 0.7
            }, {
                id: 12,
                type: 1,
                age: 8,
                pre: 9,
                name: "crossbow",
                desc: "deals more damage and has greater range",
                src: "crossbow_1",
                req: ["wood", 5],
                aboveHand: true,
                armS: 0.75,
                length: 120,
                width: 120,
                xOff: -4,
                yOff: 0,
                projectile: 2,
                spdMult: 0.7,
                speed: 700
            }, {
                id: 13,
                type: 1,
                age: 9,
                pre: 12,
                name: "repeater crossbow",
                desc: "high firerate crossbow with reduced damage",
                src: "crossbow_2",
                req: ["wood", 10],
                aboveHand: true,
                armS: 0.75,
                length: 120,
                width: 120,
                xOff: -4,
                yOff: 0,
                projectile: 3,
                spdMult: 0.7,
                speed: 230
            }, {
                id: 14,
                type: 1,
                age: 6,
                name: "mc grabby",
                desc: "steals resources from enemies",
                src: "grab_1",
                length: 130,
                width: 210,
                xOff: -8,
                yOff: 53,
                dmg: 0,
                steal: 250,
                knock: 0.2,
                spdMult: 1.05,
                range: 125,
                gather: 0,
                speed: 700
            }, {
                id: 15,
                type: 1,
                age: 9,
                pre: 12,
                name: "musket",
                desc: "slow firerate but high damage and range",
                src: "musket_1",
                req: ["stone", 10],
                aboveHand: true,
                rec: 0.35,
                armS: 0.6,
                hndS: 0.3,
                hndD: 1.6,
                length: 205,
                width: 205,
                xOff: 25,
                yOff: 0,
                projectile: 5,
                hideProjectile: true,
                spdMult: 0.6,
                speed: 1500
            }];

            // ITEMS:
            module.exports.list = [{
                group: module.exports.groups[0],
                name: "apple",
                desc: "restores 20 health when consumed",
                req: ["food", 10],
                consume: function(doer) {
                    return doer.changeHealth(20, doer);
                },
                scale: 22,
                holdOffset: 15
            }, {
                age: 3,
                group: module.exports.groups[0],
                name: "cookie",
                desc: "restores 40 health when consumed",
                req: ["food", 15],
                consume: function(doer) {
                    return doer.changeHealth(40, doer);
                },
                scale: 27,
                holdOffset: 15
            }, {
                age: 7,
                group: module.exports.groups[0],
                name: "cheese",
                desc: "restores 30 health and another 50 over 5 seconds",
                req: ["food", 25],
                consume: function(doer) {
                    if (doer.changeHealth(30, doer) || doer.health < 100) {
                        doer.dmgOverTime.dmg = -10;
                        doer.dmgOverTime.doer = doer;
                        doer.dmgOverTime.time = 5;
                        return true;
                    }
                    return false;
                },
                scale: 27,
                holdOffset: 15
            }, {
                group: module.exports.groups[1],
                name: "wood wall",
                desc: "provides protection for your village",
                req: ["wood", 10],
                projDmg: true,
                health: 380,
                scale: 50,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 3,
                group: module.exports.groups[1],
                name: "stone wall",
                desc: "provides improved protection for your village",
                req: ["stone", 25],
                health: 900,
                scale: 50,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                pre: 1,
                group: module.exports.groups[1],
                name: "castle wall",
                desc: "provides powerful protection for your village",
                req: ["stone", 35],
                health: 1500,
                scale: 52,
                holdOffset: 20,
                placeOffset: -5
            }, {
                group: module.exports.groups[2],
                name: "spikes",
                desc: "damages enemies when they touch them",
                req: ["wood", 20, "stone", 5],
                health: 400,
                dmg: 20,
                scale: 49,
                spritePadding: -23,
                holdOffset: 8,
                placeOffset: -5
            }, {
                age: 5,
                group: module.exports.groups[2],
                name: "greater spikes",
                desc: "damages enemies when they touch them",
                req: ["wood", 30, "stone", 10],
                health: 500,
                dmg: 35,
                scale: 52,
                spritePadding: -23,
                holdOffset: 8,
                placeOffset: -5
            }, {
                age: 9,
                pre: 1,
                group: module.exports.groups[2],
                name: "poison spikes",
                desc: "poisons enemies when they touch them",
                req: ["wood", 35, "stone", 15],
                health: 600,
                dmg: 30,
                pDmg: 5,
                scale: 52,
                spritePadding: -23,
                holdOffset: 8,
                placeOffset: -5
            }, {
                age: 9,
                pre: 2,
                group: module.exports.groups[2],
                name: "spinning spikes",
                desc: "damages enemies when they touch them",
                req: ["wood", 30, "stone", 20],
                health: 500,
                dmg: 45,
                turnSpeed: 0.003,
                scale: 52,
                spritePadding: -23,
                holdOffset: 8,
                placeOffset: -5
            }, {
                group: module.exports.groups[3],
                name: "windmill",
                desc: "generates gold over time",
                req: ["wood", 50, "stone", 10],
                health: 400,
                pps: 1,
                turnSpeed: 0.0016,
                spritePadding: 25,
                iconLineMult: 12,
                scale: 45,
                holdOffset: 20,
                placeOffset: 5
            }, {
                age: 5,
                pre: 1,
                group: module.exports.groups[3],
                name: "faster windmill",
                desc: "generates more gold over time",
                req: ["wood", 60, "stone", 20],
                health: 500,
                pps: 1.5,
                turnSpeed: 0.0025,
                spritePadding: 25,
                iconLineMult: 12,
                scale: 47,
                holdOffset: 20,
                placeOffset: 5
            }, {
                age: 8,
                pre: 1,
                group: module.exports.groups[3],
                name: "power mill",
                desc: "generates more gold over time",
                req: ["wood", 100, "stone", 50],
                health: 800,
                pps: 2,
                turnSpeed: 0.005,
                spritePadding: 25,
                iconLineMult: 12,
                scale: 47,
                holdOffset: 20,
                placeOffset: 5
            }, {
                age: 5,
                group: module.exports.groups[4],
                type: 2,
                name: "mine",
                desc: "allows you to mine stone",
                req: ["wood", 20, "stone", 100],
                iconLineMult: 12,
                scale: 65,
                holdOffset: 20,
                placeOffset: 0
            }, {
                age: 5,
                group: module.exports.groups[11],
                type: 0,
                name: "sapling",
                desc: "allows you to farm wood",
                req: ["wood", 150],
                iconLineMult: 12,
                colDiv: 0.5,
                scale: 110,
                holdOffset: 50,
                placeOffset: -15
            }, {
                age: 4,
                group: module.exports.groups[5],
                name: "pit trap",
                desc: "pit that traps enemies if they walk over it",
                req: ["wood", 30, "stone", 30],
                trap: true,
                ignoreCollision: true,
                hideFromEnemy: true,
                health: 500,
                colDiv: 0.2,
                scale: 50,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 4,
                group: module.exports.groups[6],
                name: "boost pad",
                desc: "provides boost when stepped on",
                req: ["stone", 20, "wood", 5],
                ignoreCollision: true,
                boostSpeed: 1.5,
                health: 150,
                colDiv: 0.7,
                scale: 45,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                group: module.exports.groups[7],
                doUpdate: true,
                name: "turret",
                desc: "defensive structure that shoots at enemies",
                req: ["wood", 200, "stone", 150],
                health: 800,
                projectile: 1,
                shootRange: 700,
                shootRate: 2200,
                scale: 43,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                group: module.exports.groups[8],
                name: "platform",
                desc: "platform to shoot over walls and cross over water",
                req: ["wood", 20],
                ignoreCollision: true,
                zIndex: 1,
                health: 300,
                scale: 43,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                group: module.exports.groups[9],
                name: "healing pad",
                desc: "standing on it will slowly heal you",
                req: ["wood", 30, "food", 10],
                ignoreCollision: true,
                healCol: 15,
                health: 400,
                colDiv: 0.7,
                scale: 45,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 9,
                group: module.exports.groups[10],
                name: "spawn pad",
                desc: "you will spawn here when you die but it will dissapear",
                req: ["wood", 100, "stone", 100],
                health: 400,
                ignoreCollision: true,
                spawnPoint: true,
                scale: 45,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                group: module.exports.groups[12],
                name: "blocker",
                desc: "blocks building in radius",
                req: ["wood", 30, "stone", 25],
                ignoreCollision: true,
                blocker: 300,
                health: 400,
                colDiv: 0.7,
                scale: 45,
                holdOffset: 20,
                placeOffset: -5
            }, {
                age: 7,
                group: module.exports.groups[13],
                name: "teleporter",
                desc: "teleports you to a random point on the map",
                req: ["wood", 60, "stone", 60],
                ignoreCollision: true,
                teleport: true,
                health: 200,
                colDiv: 0.7,
                scale: 45,
                holdOffset: 20,
                placeOffset: -5
            }];

            // ASSIGN IDS:
            for (var i = 0; i < module.exports.list.length; ++i) {
                module.exports.list[i].id = i;
                if (module.exports.list[i].pre) module.exports.list[i].pre = i - module.exports.list[i].pre;
            }

            // TROLOLOLOL:
            if (typeof window !== "undefined") {
                function shuffle(a) {
                    for (let i = a.length - 1; i > 0; i--) {
                        const j = Math.floor(Math.random() * (i + 1));
                        [a[i], a[j]] = [a[j], a[i]];
                    }
                    return a;
                }
            }


            /***/ }),

        /***/ "./src/js/data/mapManager.js":
        /*!***********************************!*\
  !*** ./src/js/data/mapManager.js ***!
  \***********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            // GLOBAL MAPMANAGER:
            module.exports = {}

            /***/ }),

        /***/ "./src/js/data/objectManager.js":
        /*!**************************************!*\
  !*** ./src/js/data/objectManager.js ***!
  \**************************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            var mathFloor = Math.floor;
            var mathABS = Math.abs;
            var mathCOS = Math.cos;
            var mathSIN = Math.sin;
            var mathPOW = Math.pow;
            var mathSQRT = Math.sqrt;
            module.exports = function (GameObject, gameObjects, UTILS, config, players, server) {
                this.objects = gameObjects;
                this.grids = {};
                this.updateObjects = [];

                // SET OBJECT GRIDS:
                var tmpX, tmpY;
                var tmpS = config.mapScale/config.colGrid;
                this.setObjectGrids = function(obj) {
                    var objX = Math.min(config.mapScale, Math.max(0, obj.x));
                    var objY = Math.min(config.mapScale, Math.max(0, obj.y));
                    for (var x = 0; x < config.colGrid; ++x) {
                        tmpX = x * tmpS;
                        for (var y = 0; y < config.colGrid; ++y) {
                            tmpY = y * tmpS;
                            if (objX + obj.scale >= tmpX && objX - obj.scale <= tmpX + tmpS &&
                                objY + obj.scale >= tmpY && objY - obj.scale <= tmpY + tmpS) {
                                if (!this.grids[x + "_" + y])
                                    this.grids[x + "_" + y] = [];
                                this.grids[x + "_" + y].push(obj);
                                obj.gridLocations.push(x + "_" + y);
                            }
                        }
                    }
                };

                // REMOVE OBJECT FROM GRID:
                this.removeObjGrid = function(obj) {
                    var tmpIndx;
                    for (var i = 0; i < obj.gridLocations.length; ++i) {
                        tmpIndx = this.grids[obj.gridLocations[i]].indexOf(obj);
                        if (tmpIndx >= 0) {
                            this.grids[obj.gridLocations[i]].splice(tmpIndx, 1);
                        }
                    }
                };

                // DISABLE OBJ:
                this.disableObj = function(obj) {
                    obj.active = false;
                    if (server) {
                        if (obj.owner && obj.pps) obj.owner.pps -= obj.pps;
                        this.removeObjGrid(obj);
                        var tmpIndx = this.updateObjects.indexOf(obj);
                        if (tmpIndx >= 0) {
                            this.updateObjects.splice(tmpIndx, 1);
                        }
                    }
                };

                // HIT OBJECT:
                this.hitObj = function(tmpObj, tmpDir) {
                    for (var p = 0; p < players.length; ++p) {
                        if (players[p].active) {
                            if (tmpObj.sentTo[players[p].id]) {
                                if (!tmpObj.active) server.send(players[p].id, "12", tmpObj.sid);
                                else if (players[p].canSee(tmpObj))
                                    server.send(players[p].id, "8", UTILS.fixTo(tmpDir, 1), tmpObj.sid);
                            } if (!tmpObj.active && tmpObj.owner == players[p])
                                players[p].changeItemCount(tmpObj.group.id, -1);
                        }
                    }
                };

                // GET GRID ARRAY:
                var tmpArray = [];
                var tmpGrid;
                this.getGridArrays = function(xPos, yPos, s) {
                    tmpX = mathFloor(xPos / tmpS);
                    tmpY = mathFloor(yPos / tmpS);
                    tmpArray.length = 0;
                    try {
                        if (this.grids[tmpX + "_" + tmpY])
                            tmpArray.push(this.grids[tmpX + "_" + tmpY]);
                        if (xPos + s >= (tmpX + 1) * tmpS) { // RIGHT
                            tmpGrid = this.grids[(tmpX + 1) + "_" + tmpY];
                            if (tmpGrid) tmpArray.push(tmpGrid);
                            if (tmpY && yPos - s <= tmpY * tmpS) { // TOP RIGHT
                                tmpGrid = this.grids[(tmpX + 1) + "_" + (tmpY - 1)];
                                if (tmpGrid) tmpArray.push(tmpGrid);
                            } else if (yPos + s >= (tmpY + 1) * tmpS) { // BOTTOM RIGHT
                                tmpGrid = this.grids[(tmpX + 1) + "_" + (tmpY + 1)];
                                if (tmpGrid) tmpArray.push(tmpGrid);
                            }
                        } if (tmpX && xPos - s <= tmpX * tmpS) { // LEFT
                            tmpGrid = this.grids[(tmpX - 1) + "_" + tmpY];
                            if (tmpGrid) tmpArray.push(tmpGrid);
                            if (tmpY && yPos - s <= tmpY * tmpS) { // TOP LEFT
                                tmpGrid = this.grids[(tmpX - 1) + "_" + (tmpY - 1)];
                                if (tmpGrid) tmpArray.push(tmpGrid);
                            } else if (yPos + s >= (tmpY + 1) * tmpS) { // BOTTOM LEFT
                                tmpGrid = this.grids[(tmpX - 1) + "_" + (tmpY + 1)];
                                if (tmpGrid) tmpArray.push(tmpGrid);
                            }
                        } if (yPos + s >= (tmpY + 1) * tmpS) { // BOTTOM
                            tmpGrid = this.grids[tmpX + "_" + (tmpY + 1)];
                            if (tmpGrid) tmpArray.push(tmpGrid);
                        } if (tmpY && yPos - s <= tmpY * tmpS) { // TOP
                            tmpGrid = this.grids[tmpX + "_" + (tmpY - 1)];
                            if (tmpGrid) tmpArray.push(tmpGrid);
                        }
                    } catch (e) {}
                    return tmpArray;
                };

                // ADD NEW:
                var tmpObj;
                this.add = function(sid, x, y, dir, s, type, data, setSID, owner) {
                    tmpObj = null;
                    for (var i = 0; i < gameObjects.length; ++i) {
                        if (gameObjects[i].sid == sid) {
                            tmpObj = gameObjects[i];
                            break;
                        }
                    } if (!tmpObj) {
                        for (var i = 0; i < gameObjects.length; ++i) {
                            if (!gameObjects[i].active) {
                                tmpObj = gameObjects[i];
                                break;
                            }
                        }
                    } if (!tmpObj) {
                        tmpObj = new GameObject(sid);
                        gameObjects.push(tmpObj);
                    }
                    if (setSID)
                        tmpObj.sid = sid;
                    tmpObj.init(x, y, dir, s, type, data, owner);
                    if (true) {
                        this.setObjectGrids(tmpObj);
                        if (tmpObj.doUpdate)
                            this.updateObjects.push(tmpObj);
                    }
                };

                // DISABLE BY SID:
                this.disableBySid = function(sid) {
                    for (var i = 0; i < gameObjects.length; ++i) {
                        if (gameObjects[i].sid == sid) {
                            this.disableObj(gameObjects[i]);
                            break;
                        }
                    }
                };

                // REMOVE ALL FROM PLAYER:
                this.removeAllItems = function(sid, server) {
                    for (var i = 0; i < gameObjects.length; ++i) {
                        if (gameObjects[i].active && gameObjects[i].owner && gameObjects[i].owner.sid == sid) {
                            this.disableObj(gameObjects[i]);
                        }
                    }
                    if (server) {
                        server.broadcast("13", sid);
                    }
                };

                // FETCH SPAWN OBJECT:
                this.fetchSpawnObj = function(sid) {
                    var tmpLoc = null;
                    for (var i = 0; i < gameObjects.length; ++i) {
                        tmpObj = gameObjects[i];
                        if (tmpObj.active && tmpObj.owner && tmpObj.owner.sid == sid && tmpObj.spawnPoint) {
                            tmpLoc = [tmpObj.x, tmpObj.y];
                            this.disableObj(tmpObj);
                            server.broadcast("12", tmpObj.sid);
                            if (tmpObj.owner) {
                                tmpObj.owner.changeItemCount(tmpObj.group.id, -1);
                            }
                            break;
                        }
                    }
                    return tmpLoc;
                };

                // CHECK IF PLACABLE:
                this.checkItemLocation = function(x, y, s, sM, indx, ignoreWater, placer) {
                    for (var i = 0; i < gameObjects.length; ++i) {
                        var blockS = (gameObjects[i].blocker?
                                      gameObjects[i].blocker:gameObjects[i].getScale(sM, gameObjects[i].isItem));
                        if (gameObjects[i].active && UTILS.getDistance(x, y, gameObjects[i].x,
                                                                       gameObjects[i].y) < (s + blockS))
                            return false;
                    } if (!ignoreWater && indx != 18 && y >= (config.mapScale / 2) - (config.riverWidth / 2) && y <=
                          (config.mapScale / 2) + (config.riverWidth / 2)) {
                        return false;
                    }
                    return true;
                };

                // ADD PROJECTILE:
                this.addProjectile = function(x, y, dir, range, indx) {
                    var tmpData = items.projectiles[indx];
                    var tmpProj;
                    for (var i = 0; i < projectiles.length; ++i) {
                        if (!projectiles[i].active) {
                            tmpProj = projectiles[i];
                            break;
                        }
                    }
                    if (!tmpProj) {
                        tmpProj = new Projectile(players, UTILS);
                        projectiles.push(tmpProj);
                    }
                    tmpProj.init(indx, x, y, dir, tmpData.speed, range, tmpData.scale);
                };

                // CHECK PLAYER COLLISION:
                this.checkCollision = function(player, other, delta) {
                    delta = delta||1;
                    var dx = player.x2 - other.x;
                    var dy = player.y2 - other.y;
                    var tmpLen = player.scale + other.scale;
                    if (mathABS(dx) <= tmpLen || mathABS(dy) <= tmpLen) {
                        tmpLen = player.scale + (other.getScale?other.getScale():other.scale);
                        var tmpInt = mathSQRT(dx * dx + dy * dy) - tmpLen;
                        if (tmpInt <= 0) {
                            if (!other.ignoreCollision) {
                                var tmpDir = UTILS.getDirection(player.x2, player.y2, other.x, other.y);
                                var tmpDist = UTILS.getDistance(player.x2, player.y2, other.x, other.y);
                                if (other.isPlayer) {
                                    tmpInt = (tmpInt * -1) / 2;
                                    // player.x += (tmpInt * mathCOS(tmpDir));
                                    // player.y += (tmpInt * mathSIN(tmpDir));
                                    // other.x -= (tmpInt * mathCOS(tmpDir));
                                    // other.y -= (tmpInt * mathSIN(tmpDir));
                                } else {
                                    // player.x = other.x + (tmpLen * mathCOS(tmpDir));
                                    //  player.y = other.y + (tmpLen * mathSIN(tmpDir));
                                    player.xVel *= 0.75;
                                    player.yVel *= 0.75;
                                }
                                if (other.dmg && other.owner != player && !(other.owner &&
                                                                            other.owner.team && other.owner.team == player.team)) {
                                    player.changeHealth(-other.dmg, other.owner, other);
                                    var tmpSpd = 1.5 * (other.weightM||1);
                                    player.xVel += tmpSpd * mathCOS(tmpDir);
                                    player.yVel += tmpSpd * mathSIN(tmpDir);
                                    if (other.pDmg && !(player.skin && player.skin.poisonRes)) {
                                        player.dmgOverTime.dmg = other.pDmg;
                                        player.dmgOverTime.time = 5;
                                        player.dmgOverTime.doer = other.owner;
                                    }
                                    if (player.colDmg && other.health) {
                                        //if (other.changeHealth(-player.colDmg)) this.disableObj(other);
                                        this.hitObj(other, UTILS.getDirection(player.x2, player.y2, other.x, other.y));
                                    }
                                }
                            } else if (other.trap && !player.noTrap && other.owner != player && !(other.owner &&
                                                                                                  other.owner.team && other.owner.team == player.team)) {
                                player.lockMove = true;
                                other.hideFromEnemy = false;
                            } else if (other.boostSpeed) {
                                player.xVel += (delta * other.boostSpeed * (other.weightM||1)) * mathCOS(other.dir);
                                player.yVel += (delta * other.boostSpeed * (other.weightM||1)) * mathSIN(other.dir);
                            } else if (other.healCol) {
                                player.healCol = other.healCol;
                            } else if (other.teleport) {
                                // player.x = UTILS.randInt(0, config.mapScale);
                                // player.y = UTILS.randInt(0, config.mapScale);
                            }
                            if (other.zIndex > player.zIndex) player.zIndex = other.zIndex;
                            return true;
                        }
                    }
                    return false;
                };

            };


            /***/ }),

        /***/ "./src/js/data/player.js":
        /*!*******************************!*\
  !*** ./src/js/data/player.js ***!
  \*******************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            var mathABS = Math.abs;
            var mathCOS = Math.cos;
            var mathSIN = Math.sin;
            var mathPOW = Math.pow;
            var mathSQRT = Math.sqrt;
            module.exports = function(id, sid, config, UTILS, projectileManager,
                                       objectManager, players, ais, items, hats, accessories, server, scoreCallback, iconCallback) {
                this.id = id;
                this.sid = sid;
                this.tmpScore = 0;
                this.team = null;
                this.skinIndex = 0;
                this.tailIndex = 0;
                this.hitTime = 0;
                this.tails = {};
                for (var i = 0; i < accessories.length; ++i) {
                    if (accessories[i].price <= 0)
                        this.tails[accessories[i].id] = 1;
                }
                this.skins = {};
                for (var i = 0; i < hats.length; ++i) {
                    if (hats[i].price <= 0)
                        this.skins[hats[i].id] = 1;
                }
                this.points = 0;
                this.dt = 0;
                this.hidden = false;
                this.itemCounts = {};
                this.isPlayer = true;
                this.pps = 0;
                this.moveDir = undefined;
                this.skinRot = 0;
                this.lastPing = 0;
                this.iconIndex = 0;
                this.skinColor = 0;

                // SPAWN:
                this.spawn = function(moofoll) {
                    this.active = true;
                    this.alive = true;
                    this.lockMove = false;
                    this.lockDir = false;
                    this.minimapCounter = 0;
                    this.chatCountdown = 0;
                    this.shameCount = 0;
                    this.shameTimer = 0;
                    this.sentTo = {};
                    this.gathering = 0;
                    this.autoGather = 0;
                    this.animTime = 0;
                    this.animSpeed = 0;
                    this.mouseState = 0;
                    this.buildIndex = -1;
                    this.weaponIndex = 0;
                    this.dmgOverTime = {};
                    this.noMovTimer = 0;
                    this.maxXP = 300;
                    this.XP = 0;
                    this.age = 1;
                    this.kills = 0;
                    this.upgrAge = 2;
                    this.upgradePoints = 0;
                    this.x = 0;
                    this.y = 0;
                    this.zIndex = 0;
                    this.xVel = 0;
                    this.yVel = 0;
                    this.slowMult = 1;
                    this.dir = 0;
                    this.dirPlus = 0;
                    this.targetDir = 0;
                    this.targetAngle = 0;
                    this.maxHealth = 100;
                    this.health = this.maxHealth;
                    this.scale = config.playerScale;
                    this.speed = config.playerSpeed;
                    this.resetMoveDir();
                    this.resetResources(moofoll);
                    this.items = [0, 3, 6, 10];
                    this.weapons = [0];
                    this.shootCount = 0;
                    this.weaponXP = [];
                    this.reloads = [{count: Math.ceil(300 / 111), date: 0, limit: Math.ceil(300 / 111), limit_: 300, ready: true, id: 5, val: 0},
                                    {count: Math.ceil(1500 / 111), date: 0, limit: Math.ceil(1500 / 111), limit_: 1500, ready: true, id: 15, val: 0},
                                    {count: 23, date: 0, limit: 23, ready: true, limit_: 2500}];
                };

                // RESET MOVE DIR:
                this.resetMoveDir = function() {
                    this.moveDir = undefined;
                };

                // RESET RESOURCES:
                this.resetResources = function(moofoll) {
                    for (var i = 0; i < config.resourceTypes.length; ++i) {
                        this[config.resourceTypes[i]] = moofoll?100:0;
                    }
                };

                // ADD ITEM:
                this.addItem = function(id) {
                    var tmpItem = items.list[id];
                    if (tmpItem) {
                        for (var i = 0; i < this.items.length; ++i) {
                            if (items.list[this.items[i]].group == tmpItem.group) {
                                if (this.buildIndex == this.items[i])
                                    this.buildIndex = id;
                                this.items[i] = id;
                                return true;
                            }
                        }
                        this.items.push(id);
                        return true;
                    }
                    return false;
                };

                // SET USER DATA:
                this.setUserData = function(data) {
                    if (data) {
                        // SET INITIAL NAME:
                        this.name = "unknown";

                        // VALIDATE NAME:
                        var name = data.name + "";
                        name = name.slice(0, config.maxNameLength);
                        name = name.replace(/[^\w:\(\)\/? -]+/gmi, " ");  // USE SPACE SO WE CAN CHECK PROFANITY
                        name = name.replace(/[^\x00-\x7F]/g, " ");
                        name = name.trim();

                        // CHECK IF IS PROFANE:
                        var isProfane = false;

                        if (name.length > 0 && !isProfane) {
                            this.name = name;
                        }

                        // SKIN:
                        this.skinColor = 0;
                        if (config.skinColors[data.skin])
                            this.skinColor = data.skin;
                    }
                };

                // GET DATA TO SEND:
                this.getData = function() {
                    return [
                        this.id,
                        this.sid,
                        this.name,
                        UTILS.fixTo(this.x, 2),
                        UTILS.fixTo(this.y, 2),
                        UTILS.fixTo(this.dir, 3),
                        this.health,
                        this.maxHealth,
                        this.scale,
                        this.skinColor
                    ];
                };

                // SET DATA:
                this.setData = function(data) {
                    this.id = data[0];
                    this.sid = data[1];
                    this.name = data[2];
                    this.x = data[3];
                    this.y = data[4];
                    this.dir = data[5];
                    this.health = data[6];
                    this.maxHealth = data[7];
                    this.scale = data[8];
                    this.skinColor = data[9];
                };

                // UPDATE:
                var timerCount = 0;
                this.update = function(delta) {
                    if (!this.alive) return;

                    const Weapon = items.weapons[this.weaponIndex];
                    let Reload = this.reloads[Number(this.weaponIndex > 8)]; /* Secondary / Primary */

                    const variantValue = (() => {
                        const variants = [1, 1.1, 1.18, 1.18];

                        return variants[this.weaponVariant];
                    })();

                    if(Reload.id != Weapon.id) {
                        Reload.id = Weapon.id;
                        Reload.limit = Weapon.speed ? (Math.ceil(Weapon.speed / (1e3 / 9))) : 0;
                        Reload.limit_ = Weapon.speed;
                        Reload.count = Reload.limit;
                        Reload.ready = true;
                        Reload.val = variantValue;
                    }

                    if(variantValue != Reload.val) Reload.val = variantValue;

                    if(Reload.count < Reload.limit && this.buildIndex == -1){
                        Reload.count += 1;
                        Reload.ready = Reload.count == Reload.limit;
                    }

                    this.reloads[Number(this.weaponIndex > 8)] = Reload;
                    if(this.reloads[2].count < 23) {
                        this.reloads[2].count += 1;
                        if(this.reloads[2].count === 23) this.reloads[2].ready = true;
                    }

                    // SHAME SHAME SHAME:
                    if (this.shameTimer > 0) {
                        this.shameTimer -= delta;
                        if (this.shameTimer <= 0) {
                            this.shameTimer = 0;
                            this.shameCount = 0;
                        }
                    }

                    // REGENS AND AUTO:
                    timerCount -= delta;
                    if (timerCount <= 0) {
                        var regenAmount = (this.skin && this.skin.healthRegen?this.skin.healthRegen:0) +
                            (this.tail && this.tail.healthRegen?this.tail.healthRegen:0);
                        if (regenAmount) {
                            console.log("regenAmnt", regenAmount);
                            //  this.changeHealth(regenAmount, this);
                        } if (this.dmgOverTime.dmg) {
                            console.log("dOT", -this.dmgOverTime.dmg);
                            // this.changeHealth(-this.dmgOverTime.dmg, this.dmgOverTime.doer);
                            this.dmgOverTime.time -= 1;
                            if (this.dmgOverTime.time <= 0)
                                this.dmgOverTime.dmg = 0;
                        } if (this.healCol) {
                            console.log("hc", this.healCol);
                            //   this.changeHealth(this.healCol, this);
                        }
                        timerCount = 1000;
                    }

                    // CHECK KILL:
                    //if (!this.alive) return;

                    // SLOWER:
                    if (this.slowMult < 1) {
                        this.slowMult += 0.0008 * delta;
                        if (this.slowMult > 1)
                            this.slowMult = 1;
                    }

                    // MOVE:
                    this.noMovTimer += delta;
                    if (this.xVel || this.yVel) this.noMovTimer = 0;
                    if (this.lockMove) {
                        this.xVel = 0;
                        this.yVel = 0;
                    } else {
                        var spdMult = ((this.buildIndex>=0)?0.5:1) * (items.weapons[this.weaponIndex].spdMult||1) *
                            (this.skin?(this.skin.spdMult||1):1) * (this.tail?(this.tail.spdMult||1):1) * (this.y2<=config.snowBiomeTop?
                                                                                                           ((this.skin&&this.skin.coldM)?1:config.snowSpeed):1) * this.slowMult;
                        if (!this.zIndex && this.y2 >= (config.mapScale / 2) - (config.riverWidth / 2) &&
                            this.y2 <= (config.mapScale / 2) + (config.riverWidth / 2)) {
                            if (this.skin && this.skin.watrImm) {
                                spdMult *= 0.75;
                                this.xVel += config.waterCurrent * 0.4 * delta;
                            } else {
                                spdMult *= 0.33;
                                this.xVel += config.waterCurrent * delta;
                            }
                        }
                        var xVel = (this.moveDir!=undefined)?mathCOS(this.moveDir):0;
                        var yVel = (this.moveDir!=undefined)?mathSIN(this.moveDir):0;
                        var length = mathSQRT(xVel * xVel + yVel * yVel);
                        if (length != 0) {
                            xVel /= length;
                            yVel /= length;
                        }

                        if (xVel) this.xVel += xVel * this.speed * spdMult * delta;
                        if (yVel) this.yVel += yVel * this.speed * spdMult * delta;
                    }

                    // OBJECT COLL:
                    this.zIndex = 0;
                    this.lockMove = false;
                    this.healCol = 0;
                    var tmpList;
                    var tmpSpeed = UTILS.getDistance(0, 0, this.xVel * delta, this.yVel * delta);
                    var depth = Math.min(4, Math.max(1, Math.round(tmpSpeed / 40)));
                    var tMlt = 1 / depth;
                    for (var i = 0; i < depth; ++i) {
                        if (this.xVel){
                            // this.x += (this.xVel * delta) * tMlt;
                        }
                        if (this.yVel){
                            //console.log("yVel", (this.yVel * delta) * tMlt)
                            //  this.y += (this.yVel * delta) * tMlt;
                        }
                        tmpList = objectManager.getGridArrays(this.x, this.y, this.scale);
                        for (var x = 0; x < tmpList.length; ++x) {
                            for (var y = 0; y < tmpList[x].length; ++y) {
                                if (tmpList[x][y].active) objectManager.checkCollision(this, tmpList[x][y], tMlt);
                            }
                        }
                    }

                    // PLAYER COLLISIONS:
                    var tmpIndx = players.indexOf(this);
                    for (var i = tmpIndx + 1; i < players.length; ++i) {
                        if (players[i] != this && players[i].alive) objectManager.checkCollision(this, players[i]);
                    }

                    // DECEL:
                    if (this.xVel) {
                        this.xVel *= mathPOW(config.playerDecel, delta);
                        if (this.xVel <= 0.01 && this.xVel >= -0.01) this.xVel = 0;
                    } if (this.yVel) {
                        this.yVel *= mathPOW(config.playerDecel, delta);
                        if (this.yVel <= 0.01 && this.yVel >= -0.01) this.yVel = 0;
                    }

                    // MAP BOUNDARIES:
                    /* if (this.x - this.scale < 0) {
                        this.x = this.scale;
                    } else if (this.x + this.scale > config.mapScale) {
                        this.x = config.mapScale - this.scale;
                    } if (this.y - this.scale < 0) {
                        this.y = this.scale;
                    } else if (this.y + this.scale > config.mapScale) {
                        this.y = config.mapScale - this.scale;
                    }*/

                    // USE WEAPON OR TOOL:
                    if (this.buildIndex < 0) {
                        if (this.reloads[this.weaponIndex] > 0) {
                            // this.reloads[this.weaponIndex] -= delta;
                            this.gathering = this.mouseState;
                        } else if (this.gathering || this.autoGather) {
                            var worked = true;
                            if (items.weapons[this.weaponIndex].gather != undefined) {
                                //   this.gather(players);
                            } else if (items.weapons[this.weaponIndex].projectile != undefined &&
                                       this.hasRes(items.weapons[this.weaponIndex], (this.skin?this.skin.projCost:0))) {
                                //   this.useRes(items.weapons[this.weaponIndex], (this.skin?this.skin.projCost:0));
                                this.noMovTimer = 0;
                                var tmpIndx = items.weapons[this.weaponIndex].projectile;
                                var projOffset = this.scale * 2;
                                var aMlt = (this.skin&&this.skin.aMlt)?this.skin.aMlt:1;
                                if (items.weapons[this.weaponIndex].rec) {
                                    this.xVel -= items.weapons[this.weaponIndex].rec * mathCOS(this.dir);
                                    this.yVel -= items.weapons[this.weaponIndex].rec * mathSIN(this.dir);
                                }
                                /* projectileManager.addProjectile(this.x+(projOffset*mathCOS(this.dir)),
                                                                this.y+(projOffset*mathSIN(this.dir)), this.dir, items.projectiles[tmpIndx].range*aMlt,
                                                                items.projectiles[tmpIndx].speed*aMlt, tmpIndx, this, null, this.zIndex);*/
                            } else {
                                worked = false;
                            }
                            this.gathering = this.mouseState;
                            if (worked) {
                                // this.reloads[this.weaponIndex] = items.weapons[this.weaponIndex].speed*(this.skin?(this.skin.atkSpd||1):1);
                            }
                        }
                    }

                    // console.log("a", Math.round(this.xVel * 240), Math.round(this.yVel * 240))
                };

                // ADD WEAPON XP:
                this.addWeaponXP = function(amnt) {
                    if (!this.weaponXP[this.weaponIndex])
                        this.weaponXP[this.weaponIndex] = 0;
                    this.weaponXP[this.weaponIndex] += amnt;
                };

                // EARN XP:
                this.earnXP = function(amount) {
                    if (this.age < config.maxAge) {
                        this.XP += amount;
                        if (this.XP >= this.maxXP) {
                            if (this.age < config.maxAge) {
                                this.age++;
                                this.XP = 0;
                                this.maxXP *= 1.2;
                            } else {
                                this.XP = this.maxXP;
                            }
                            this.upgradePoints++;
                            server.send(this.id, "16", this.upgradePoints, this.upgrAge);
                            server.send(this.id, "15", this.XP, UTILS.fixTo(this.maxXP, 1), this.age);
                        } else {
                            server.send(this.id, "15", this.XP);
                        }
                    }
                };

                // CHANGE HEALTH:
                this.changeHealth = function(amount, doer) {
                    return
                    if (amount > 0 && this.health >= this.maxHealth)
                        return false
                    if (amount < 0 && this.skin)
                        amount *= this.skin.dmgMult||1;
                    if (amount < 0 && this.tail)
                        amount *= this.tail.dmgMult||1;
                    if (amount < 0)
                        this.hitTime = Date.now();
                    this.health += amount;
                    if (this.health > this.maxHealth) {
                        amount -= (this.health - this.maxHealth);
                        this.health = this.maxHealth;
                    }
                    /*if (this.health <= 0)
                        this.kill(doer);
                    for (var i = 0; i < players.length; ++i) {
                        if (this.sentTo[players[i].id])
                            server.send(players[i].id, "h", this.sid, Math.round(this.health));
                    }
                    if (doer && doer.canSee(this) && !(doer == this && amount < 0)) {
                        server.send(doer.id, "t", Math.round(this.x),
                                    Math.round(this.y), Math.round(-amount), 1);
                    }*/
                    return true;
                };

                // KILL:
                this.kill = function(doer) {
                    if (doer && doer.alive) {
                        doer.kills++;
                        if (doer.skin && doer.skin.goldSteal) scoreCallback(doer, Math.round(this.points / 2));
                        else scoreCallback(doer, Math.round(this.age*100*((doer.skin&&doer.skin.kScrM)?doer.skin.kScrM:1)));
                        server.send(doer.id, "9", "kills", doer.kills, 1);
                    }
                    this.alive = false;
                    server.send(this.id, "11");
                    iconCallback();
                };

                // ADD RESOURCE:
                this.addResource = function(type, amount, auto) {
                    if (!auto && amount > 0)
                        this.addWeaponXP(amount);
                    if (type == 3) {
                        scoreCallback(this, amount, true);
                    } else {
                        this[config.resourceTypes[type]] += amount;
                        server.send(this.id, "9", config.resourceTypes[type], this[config.resourceTypes[type]], 1);
                    }
                };

                // CHANGE ITEM COUNT:
                this.changeItemCount = function(index, value) {
                    this.itemCounts[index] = this.itemCounts[index]||0;
                    this.itemCounts[index] += value;
                    server.send(this.id, "14", index, this.itemCounts[index]);
                };

                // BUILD:
                this.buildItem = function(item) {
                    var tmpS = (this.scale + item.scale + (item.placeOffset||0));
                    var tmpX = this.x + (tmpS * mathCOS(this.dir));
                    var tmpY = this.y + (tmpS * mathSIN(this.dir));
                    if (this.canBuild(item) && !(item.consume && (this.skin && this.skin.noEat))
                        && (item.consume || objectManager.checkItemLocation(tmpX, tmpY, item.scale,
                                                                            0.6, item.id, false, this))) {
                        var worked = false;
                        if (item.consume) {
                            if (this.hitTime) {
                                var timeSinceHit = Date.now() - this.hitTime;
                                this.hitTime = 0;
                                if (timeSinceHit <= 120) {
                                    this.shameCount++;
                                    if (this.shameCount >= 8) {
                                        this.shameTimer = 30000;
                                        this.shameCount = 0;
                                    }
                                } else {
                                    this.shameCount -= 2;
                                    if (this.shameCount <= 0) {
                                        this.shameCount = 0;
                                    }
                                }
                            }
                            if (this.shameTimer <= 0)
                                worked = item.consume(this);
                        } else {
                            worked = true;
                            if (item.group.limit) {
                                this.changeItemCount(item.group.id, 1);
                            }
                            if (item.pps)
                                this.pps += item.pps;
                            objectManager.add(objectManager.objects.length, tmpX, tmpY, this.dir, item.scale,
                                              item.type, item, false, this);
                        }
                        if (worked) {
                            this.useRes(item);
                            this.buildIndex = -1;
                        }
                    }
                };

                // HAS RESOURCES:
                this.hasRes = function(item, mult) {
                    for (var i = 0; i < item.req.length;) {
                        if (this[item.req[i]] < Math.round(item.req[i + 1] * (mult||1)))
                            return false;
                        i+=2;
                    }
                    return true;
                };

                // USE RESOURCES:
                this.useRes = function(item, mult) {
                    if (config.inSandbox)
                        return;
                    for (var i = 0; i < item.req.length;) {
                        this.addResource(config.resourceTypes.indexOf(item.req[i]), -Math.round(item.req[i+1]*(mult||1)));
                        i+=2;
                    }
                };

                // CAN BUILD:
                this.canBuild = function(item) {
                    if (config.inSandbox)
                        return true;
                    if (item.group.limit && this.itemCounts[item.group.id] >= item.group.limit)
                        return false;
                    return this.hasRes(item);
                };

                // GATHER:
                this.gather = function() {

                    // SHOW:
                    this.noMovTimer = 0;

                    // SLOW MOVEMENT:
                    this.slowMult -= (items.weapons[this.weaponIndex].hitSlow||0.3);
                    if (this.slowMult < 0) this.slowMult = 0;

                    // VARIANT DMG:
                    var tmpVariant = config.fetchVariant(this);
                    var applyPoison = tmpVariant.poison;
                    var variantDmg = tmpVariant.val;

                    // CHECK IF HIT GAME OBJECT:
                    var hitObjs = {};
                    var tmpDist, tmpDir, tmpObj, hitSomething;
                    var tmpList = objectManager.getGridArrays(this.x2, this.y2, items.weapons[this.weaponIndex].range);

                    for (var t = 0; t < tmpList.length; ++t) {
                        for (var i = 0; i < tmpList[t].length; ++i) {
                            tmpObj = tmpList[t][i];
                            if (tmpObj.active && !tmpObj.dontGather && !hitObjs[tmpObj.sid] && tmpObj.visibleToPlayer(this)) {
                                tmpDist = UTILS.getDistance(this.x2, this.y2, tmpObj.x, tmpObj.y) - tmpObj.scale;
                                if (tmpDist <= items.weapons[this.weaponIndex].range) {
                                    tmpDir = UTILS.getDirection(tmpObj.x, tmpObj.y, this.x2, this.y2);
                                    if (UTILS.getAngleDist(tmpDir, this.d2) <= config.gatherAngle) {
                                        hitObjs[tmpObj.sid] = 1;
                                        if (tmpObj.health) {
                                            tmpObj.changeHealth(-items.weapons[this.weaponIndex].dmg*(variantDmg)*
                                                                (items.weapons[this.weaponIndex].sDmg||1)*(this.skinIndex === 40 ? 3.3 : 1), this)
                                            //  console.log("bl", tmpObj.health);
                                        } else {
                                            /*this.earnXP(4*items.weapons[this.weaponIndex].gather);
                                            var count = items.weapons[this.weaponIndex].gather+(tmpObj.type==3?4:0);
                                            if (this.skin && this.skin.extraGold) {
                                                this.addResource(3, 1);
                                            } this.addResource(tmpObj.type, count);*/
                                        }
                                        hitSomething = true;
                                        //objectManager.hitObj(tmpObj, tmpDir);
                                    }
                                }
                            }
                        }
                    }

                    // CHECK IF HIT PLAYER:
                    for (let i = 0; i < players.length + ais.length; ++i) {
                        tmpObj = players[i]||ais[i-players.length];
                        if (tmpObj != this && tmpObj.visible && !(tmpObj.team && tmpObj.team == this.team)) {
                            tmpDist = UTILS.getDistance(this.x2, this.y2, tmpObj.x2, tmpObj.y2) - (tmpObj.scale * 1.8);
                            if (tmpDist <= items.weapons[this.weaponIndex].range) {
                                tmpDir = UTILS.getDirection(tmpObj.x2, tmpObj.y2, this.x2, this.y2);
                                if (UTILS.getAngleDist(tmpDir, this.d2) <= config.gatherAngle) {

                                    // STEAL RESOURCES:
                                    var stealCount = items.weapons[this.weaponIndex].steal;
                                    if (stealCount && tmpObj.addResource) {
                                        /*stealCount = Math.min((tmpObj.points||0), stealCount);
                                        this.addResource(3, stealCount);
                                        tmpObj.addResource(3, -stealCount);*/
                                    }

                                    // MELEE HIT PLAYER:
                                    var dmgMlt = variantDmg;
                                    if (tmpObj.weaponIndex != undefined && items.weapons[tmpObj.weaponIndex].shield &&
                                        UTILS.getAngleDist(tmpDir+Math.PI, tmpObj.dir) <= config.shieldAngle) {
                                        dmgMlt = items.weapons[tmpObj.weaponIndex].shield;
                                    }
                                    var dmgVal = items.weapons[this.weaponIndex].dmg *
                                        (this.skin && this.skin.dmgMultO?this.skin.dmgMultO:1) *
                                        (this.tail && this.tail.dmgMultO?this.tail.dmgMultO:1);
                                    var tmpSpd = (0.3 * (tmpObj.weightM||1)) + (items.weapons[this.weaponIndex].knock||0);
                                    tmpObj.xVel += tmpSpd * mathCOS(tmpDir);
                                    tmpObj.yVel += tmpSpd * mathSIN(tmpDir);

                                    let dmg = 0;

                                    if (this.skin && this.skin.healD) dmg = dmgVal * dmgMlt * this.skin.healD;
                                    if (this.tail && this.tail.healD) dmg = dmgVal * dmgMlt * this.tail.healD;
                                    if (tmpObj.skin && tmpObj.skin.dmg && dmgMlt == 1) dmg = -dmgVal * tmpObj.skin.dmg;
                                    if (tmpObj.tail && tmpObj.tail.dmg && dmgMlt == 1) dmg = -dmgVal * tmpObj.tail.dmg;
                                    if (tmpObj.dmgOverTime && this.skin && this.skin.poisonDmg &&
                                        !(tmpObj.skin && tmpObj.skin.poisonRes)) {
                                        tmpObj.dmgOverTime.dmg = this.skin.poisonDmg;
                                        tmpObj.dmgOverTime.time = this.skin.poisonTime||1;
                                        tmpObj.dmgOverTime.doer = this;
                                    } if (tmpObj.dmgOverTime && applyPoison &&
                                          !(tmpObj.skin && tmpObj.skin.poisonRes)) {
                                        tmpObj.dmgOverTime.dmg = 5;
                                        tmpObj.dmgOverTime.time = 5;
                                        tmpObj.dmgOverTime.doer = this;
                                    } if (tmpObj.skin && tmpObj.skin.dmgK) {
                                        this.xVel -= tmpObj.skin.dmgK * mathCOS(tmpDir);
                                        this.yVel -= tmpObj.skin.dmgK * mathSIN(tmpDir);
                                    }

                                    dmg = -dmgVal * dmgMlt;

                                    console.log(`${this.name} -> ${tmpObj.name}`, dmg)
                                }
                            }
                        }
                    }

                    // SEND FOR ANIMATION:
                    // this.sendAnimation(hitSomething?1:0);
                };

                // SEND ANIMATION:
                this.sendAnimation = function(hit) {
                    for (var i = 0; i < players.length; ++i) {
                        if (this.sentTo[players[i].id] && this.canSee(players[i])) {
                            server.send(players[i].id, "7", this.sid, hit?1:0, this.weaponIndex);
                        }
                    }
                };

                // ANIMATE:
                var tmpRatio = 0;
                var animIndex = 0;
                this.animate = function(delta) {
                    if (this.animTime > 0) {
                        this.animTime -= delta;
                        if (this.animTime <= 0) {
                            this.animTime = 0;
                            this.dirPlus = 0;
                            tmpRatio = 0;
                            animIndex = 0;
                        } else {
                            if (animIndex == 0) {
                                tmpRatio += delta / (this.animSpeed * config.hitReturnRatio);
                                this.dirPlus = UTILS.lerp(0, this.targetAngle, Math.min(1, tmpRatio));
                                if (tmpRatio >= 1) {
                                    tmpRatio = 1;
                                    animIndex = 1;
                                }
                            } else {
                                tmpRatio -= delta / (this.animSpeed * (1-config.hitReturnRatio));
                                this.dirPlus = UTILS.lerp(0, this.targetAngle, Math.max(0, tmpRatio));
                            }
                        }
                    }
                };

                // GATHER ANIMATION:
                this.startAnim = function(didHit, index) {
                    this.animTime = this.animSpeed = items.weapons[index].speed;
                    this.targetAngle = (didHit?-config.hitAngle:-Math.PI);
                    tmpRatio = 0;
                    animIndex = 0;

                    const variantValue = (() => {
                        const variants = [1, 1.1, 1.18, 1.18];

                        return variants[this.weaponVariant];
                    })();

                    const angleDist = (e, t) => {
                        const i = Math.abs(t - e) % (Math.PI * 2);
                        return i > Math.PI ? Math.PI * 2 - i : i
                    }

                    const weapon = items.weapons[this.weaponIndex];
                    let reload = this.reloads[Number(index > 8)];

                    reload.count = 0;
                    reload.date = Date.now();
                    reload.ready = false;

                    this.gather();
                };

                // CAN SEE:
                this.canSee = function(other) {
                    if (!other) return false;
                    if (other.skin && other.skin.invisTimer && other.noMovTimer
                        >= other.skin.invisTimer) return false;
                    var dx = mathABS(other.x - this.x) - other.scale;
                    var dy = mathABS(other.y - this.y) - other.scale;
                    return dx <= (config.maxScreenWidth / 2) * 1.3 && dy <= (config.maxScreenHeight / 2) * 1.3;
                };

            };


            /***/ }),

        /***/ "./src/js/data/projectile.js":
        /*!***********************************!*\
  !*** ./src/js/data/projectile.js ***!
  \***********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            module.exports = function (players, ais, objectManager, items, config, UTILS, server) {

                // INIT:
                this.init = function(indx, x, y, dir, spd, dmg, rng, scl, owner) {
                    this.active = true;
                    this.indx = indx;
                    this.x = x;
                    this.y = y;
                    this.dir = dir;
                    this.skipMov = true;
                    this.speed = spd;
                    this.dmg = dmg;
                    this.scale = scl;
                    this.range = rng;
                    this.owner = owner;
                    if (server)
                        this.sentTo = {};
                };

                // UPDATE:
                var objectsHit = [];
                var tmpObj;
                this.update = function(delta) {
                    if (this.active) {
                        var tmpSpeed = this.speed * delta;
                        var tmpScale;
                        if (!this.skipMov) {
                            this.x += tmpSpeed * Math.cos(this.dir);
                            this.y += tmpSpeed * Math.sin(this.dir);
                            this.range -= tmpSpeed;
                            if (this.range <= 0) {
                                this.x += this.range * Math.cos(this.dir);
                                this.y += this.range * Math.sin(this.dir);
                                tmpSpeed = 1;
                                this.range = 0;
                                this.active = false;
                            }
                        } else {
                            this.skipMov = false;
                        }
                        if (server) {
                            for (var i = 0; i < players.length; ++i) {
                                if (!this.sentTo[players[i].id] && players[i].canSee(this)) {
                                    this.sentTo[players[i].id] = 1;
                                    server.send(players[i].id, "18", UTILS.fixTo(this.x, 1), UTILS.fixTo(this.y, 1),
                                                UTILS.fixTo(this.dir, 2), UTILS.fixTo(this.range, 1), this.speed, this.indx, this.layer, this.sid);
                                }
                            }
                            objectsHit.length = 0;
                            for (var i = 0; i < players.length + ais.length; ++i) {
                                tmpObj = players[i]||ais[i-players.length];
                                if (tmpObj.visible && tmpObj != this.owner && !(this.owner.team && tmpObj.team == this.owner.team)) {
                                    if (UTILS.lineInRect(tmpObj.x-tmpObj.scale, tmpObj.y-tmpObj.scale, tmpObj.x+tmpObj.scale,
                                                         tmpObj.y+tmpObj.scale, this.x, this.y, this.x+(tmpSpeed*Math.cos(this.dir)),
                                                         this.y+(tmpSpeed*Math.sin(this.dir)))) {
                                        objectsHit.push(tmpObj);
                                    }
                                }
                            }
                            var tmpList = objectManager.getGridArrays(this.x, this.y, this.scale);
                            for (var x = 0; x < tmpList.length; ++x) {
                                for (var y = 0; y < tmpList[x].length; ++y) {
                                    tmpObj = tmpList[x][y];
                                    tmpScale = tmpObj.getScale();
                                    if (tmpObj.active && !(this.ignoreObj == tmpObj.sid) && (this.layer <= tmpObj.layer) &&
                                        objectsHit.indexOf(tmpObj) < 0 && !tmpObj.ignoreCollision && UTILS.lineInRect(tmpObj.x-tmpScale, tmpObj.y-tmpScale, tmpObj.x+tmpScale, tmpObj.y+tmpScale,
                                                                                                                      this.x, this.y, this.x+(tmpSpeed*Math.cos(this.dir)), this.y+(tmpSpeed*Math.sin(this.dir)))) {
                                        objectsHit.push(tmpObj);
                                    }
                                }
                            }

                            // HIT OBJECTS:
                            if (objectsHit.length > 0) {
                                var hitObj = null;
                                var shortDist = null;
                                var tmpDist = null;
                                for (var i = 0; i < objectsHit.length; ++i) {
                                    tmpDist = UTILS.getDistance(this.x, this.y, objectsHit[i].x, objectsHit[i].y);
                                    if (shortDist == null || tmpDist < shortDist) {
                                        shortDist = tmpDist;
                                        hitObj = objectsHit[i];
                                    }
                                }
                                if (hitObj.isPlayer || hitObj.isAI) {
                                    var tmpSd = 0.3 * (hitObj.weightM||1);
                                    hitObj.xVel += tmpSd * Math.cos(this.dir);
                                    hitObj.yVel += tmpSd * Math.sin(this.dir);
                                    if (hitObj.weaponIndex == undefined || (!(items.weapons[hitObj.weaponIndex].shield &&
                                                                              UTILS.getAngleDist(this.dir+Math.PI, hitObj.dir) <= config.shieldAngle))) {
                                        hitObj.changeHealth(-this.dmg, this.owner, this.owner);
                                    }
                                } else {
                                    if (hitObj.projDmg && hitObj.health && hitObj.changeHealth(-this.dmg)) {
                                        objectManager.disableObj(hitObj);
                                    }
                                    for (var i = 0; i < players.length; ++i) {
                                        if (players[i].active) {
                                            if (hitObj.sentTo[players[i].id]) {
                                                if (hitObj.active) {
                                                    if (players[i].canSee(hitObj))
                                                        server.send(players[i].id, "8", UTILS.fixTo(this.dir, 2), hitObj.sid);
                                                } else {
                                                    server.send(players[i].id, "12", hitObj.sid);
                                                }
                                            }
                                            if (!hitObj.active && hitObj.owner == players[i])
                                                players[i].changeItemCount(hitObj.group.id, -1);
                                        }

                                    }
                                }
                                this.active = false;
                                for (var i = 0; i < players.length; ++i) {
                                    if (this.sentTo[players[i].id])
                                        server.send(players[i].id, "19", this.sid, UTILS.fixTo(shortDist, 1));
                                }
                            }
                        }
                    }
                };
            };


            /***/ }),

        /***/ "./src/js/data/projectileManager.js":
        /*!******************************************!*\
  !*** ./src/js/data/projectileManager.js ***!
  \******************************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            module.exports = function (Projectile, projectiles, players, ais, objectManager, items, config, UTILS, server) {
                this.addProjectile = function(x, y, dir, range, speed, indx, owner, ignoreObj, layer) {
                    var tmpData = items.projectiles[indx];
                    var tmpProj;
                    for (var i = 0; i < projectiles.length; ++i) {
                        if (!projectiles[i].active) {
                            tmpProj = projectiles[i];
                            break;
                        }
                    } if (!tmpProj) {
                        tmpProj = new Projectile(players, ais, objectManager, items, config, UTILS, server);
                        tmpProj.sid = projectiles.length;
                        projectiles.push(tmpProj);
                    }
                    tmpProj.init(indx, x, y, dir, speed, tmpData.dmg, range, tmpData.scale, owner);
                    tmpProj.ignoreObj = ignoreObj;
                    tmpProj.layer = layer||tmpData.layer;
                    tmpProj.src = tmpData.src;
                    return tmpProj;
                };
            };


            /***/ }),

        /***/ "./src/js/data/store.js":
        /*!******************************!*\
  !*** ./src/js/data/store.js ***!
  \******************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // STORE HATS:
            module.exports.hats = [{
                id: 45,
                name: "Shame!",
                dontSell: true,
                price: 0,
                scale: 120,
                desc: "hacks are for losers"
            }, {
                id: 51,
                name: "Moo Cap",
                price: 0,
                scale: 120,
                desc: "coolest mooer around"
            }, {
                id: 50,
                name: "Apple Cap",
                price: 0,
                scale: 120,
                desc: "apple farms remembers"
            }, {
                id: 28,
                name: "Moo Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 29,
                name: "Pig Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 30,
                name: "Fluff Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 36,
                name: "Pandou Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 37,
                name: "Bear Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 38,
                name: "Monkey Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 44,
                name: "Polar Head",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 35,
                name: "Fez Hat",
                price: 0,
                scale: 120,
                desc: "no effect"
            }, {
                id: 42,
                name: "Enigma Hat",
                price: 0,
                scale: 120,
                desc: "join the enigma army"
            }, {
                id: 43,
                name: "Blitz Hat",
                price: 0,
                scale: 120,
                desc: "hey everybody i'm blitz"
            }, {
                id: 49,
                name: "Bob XIII Hat",
                price: 0,
                scale: 120,
                desc: "like and subscribe"
            }, {
                id: 57,
                name: "Pumpkin",
                price: 50,
                scale: 120,
                desc: "Spooooky"
            }, {
                id: 8,
                name: "Bummle Hat",
                price: 100,
                scale: 120,
                desc: "no effect"
            }, {
                id: 2,
                name: "Straw Hat",
                price: 500,
                scale: 120,
                desc: "no effect"
            }, {
                id: 15,
                name: "Winter Cap",
                price: 600,
                scale: 120,
                desc: "allows you to move at normal speed in snow",
                coldM: 1
            }, {
                id: 5,
                name: "Cowboy Hat",
                price: 1000,
                scale: 120,
                desc: "no effect"
            }, {
                id: 4,
                name: "Ranger Hat",
                price: 2000,
                scale: 120,
                desc: "no effect"
            }, {
                id: 18,
                name: "Explorer Hat",
                price: 2000,
                scale: 120,
                desc: "no effect"
            }, {
                id: 31,
                name: "Flipper Hat",
                price: 2500,
                scale: 120,
                desc: "have more control while in water",
                watrImm: true
            }, {
                id: 1,
                name: "Marksman Cap",
                price: 3000,
                scale: 120,
                desc: "increases arrow speed and range",
                aMlt: 1.3
            }, {
                id: 10,
                name: "Bush Gear",
                price: 3000,
                scale: 160,
                desc: "allows you to disguise yourself as a bush"
            }, {
                id: 48,
                name: "Halo",
                price: 3000,
                scale: 120,
                desc: "no effect"
            }, {
                id: 6,
                name: "Soldier Helmet",
                price: 4000,
                scale: 120,
                desc: "reduces damage taken but slows movement",
                spdMult: 0.94,
                dmgMult: 0.75
            }, {
                id: 23,
                name: "Anti Venom Gear",
                price: 4000,
                scale: 120,
                desc: "makes you immune to poison",
                poisonRes: 1
            }, {
                id: 13,
                name: "Medic Gear",
                price: 5000,
                scale: 110,
                desc: "slowly regenerates health over time",
                healthRegen: 3
            }, {
                id: 9,
                name: "Miners Helmet",
                price: 5000,
                scale: 120,
                desc: "earn 1 extra gold per resource",
                extraGold: 1
            }, {
                id: 32,
                name: "Musketeer Hat",
                price: 5000,
                scale: 120,
                desc: "reduces cost of projectiles",
                projCost: 0.5
            }, {
                id: 7,
                name: "Bull Helmet",
                price: 6000,
                scale: 120,
                desc: "increases damage done but drains health",
                healthRegen: -5,
                dmgMultO: 1.5,
                spdMult: 0.96
            }, {
                id: 22,
                name: "Emp Helmet",
                price: 6000,
                scale: 120,
                desc: "turrets won't attack but you move slower",
                antiTurret: 1,
                spdMult: 0.7
            }, {
                id: 12,
                name: "Booster Hat",
                price: 6000,
                scale: 120,
                desc: "increases your movement speed",
                spdMult: 1.16
            }, {
                id: 26,
                name: "Barbarian Armor",
                price: 8000,
                scale: 120,
                desc: "knocks back enemies that attack you",
                dmgK: 0.6
            }, {
                id: 21,
                name: "Plague Mask",
                price: 10000,
                scale: 120,
                desc: "melee attacks deal poison damage",
                poisonDmg: 5,
                poisonTime: 6
            }, {
                id: 46,
                name: "Bull Mask",
                price: 10000,
                scale: 120,
                desc: "bulls won't target you unless you attack them",
                bullRepel: 1
            }, {
                id: 14,
                name: "Windmill Hat",
                topSprite: true,
                price: 10000,
                scale: 120,
                desc: "generates points while worn",
                pps: 1.5
            }, {
                id: 11,
                name: "Spike Gear",
                topSprite: true,
                price: 10000,
                scale: 120,
                desc: "deal damage to players that damage you",
                dmg: 0.45
            }, {
                id: 53,
                name: "Turret Gear",
                topSprite: true,
                price: 10000,
                scale: 120,
                desc: "you become a walking turret",
                turret: {
                    proj: 1,
                    range: 700,
                    rate: 2500
                },
                spdMult: 0.7
            }, {
                id: 20,
                name: "Samurai Armor",
                price: 12000,
                scale: 120,
                desc: "increased attack speed and fire rate",
                atkSpd: 0.78
            }, {
                id: 58,
                name: "Dark Knight",
                price: 12000,
                scale: 120,
                desc: "restores health when you deal damage",
                healD: 0.4
            }, {
                id: 27,
                name: "Scavenger Gear",
                price: 15000,
                scale: 120,
                desc: "earn double points for each kill",
                kScrM: 2
            }, {
                id: 40,
                name: "Tank Gear",
                price: 15000,
                scale: 120,
                desc: "increased damage to buildings but slower movement",
                spdMult: 0.3,
                bDmg: 3.3
            }, {
                id: 52,
                name: "Thief Gear",
                price: 15000,
                scale: 120,
                desc: "steal half of a players gold when you kill them",
                goldSteal: 0.5
            }, {
                id: 55,
                name: "Bloodthirster",
                price: 20000,
                scale: 120,
                desc: "Restore Health when dealing damage. And increased damage",
                healD: 0.25,
                dmgMultO: 1.2,
            }, {
                id: 56,
                name: "Assassin Gear",
                price: 20000,
                scale: 120,
                desc: "Go invisible when not moving. Can't eat. Increased speed",
                noEat: true,
                spdMult: 1.1,
                invisTimer: 1000
            }];

            // STORE ACCESSORIES:
            module.exports.accessories = [{
                id: 12,
                name: "Snowball",
                price: 1000,
                scale: 105,
                xOff: 18,
                desc: "no effect"
            }, {
                id: 9,
                name: "Tree Cape",
                price: 1000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 10,
                name: "Stone Cape",
                price: 1000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 3,
                name: "Cookie Cape",
                price: 1500,
                scale: 90,
                desc: "no effect"
            }, {
                id: 8,
                name: "Cow Cape",
                price: 2000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 11,
                name: "Monkey Tail",
                price: 2000,
                scale: 97,
                xOff: 25,
                desc: "Super speed but reduced damage",
                spdMult: 1.35,
                dmgMultO: 0.2
            }, {
                id: 17,
                name: "Apple Basket",
                price: 3000,
                scale: 80,
                xOff: 12,
                desc: "slowly regenerates health over time",
                healthRegen: 1
            }, {
                id: 6,
                name: "Winter Cape",
                price: 3000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 4,
                name: "Skull Cape",
                price: 4000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 5,
                name: "Dash Cape",
                price: 5000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 2,
                name: "Dragon Cape",
                price: 6000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 1,
                name: "Super Cape",
                price: 8000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 7,
                name: "Troll Cape",
                price: 8000,
                scale: 90,
                desc: "no effect"
            }, {
                id: 14,
                name: "Thorns",
                price: 10000,
                scale: 115,
                xOff: 20,
                desc: "no effect"
            }, {
                id: 15,
                name: "Blockades",
                price: 10000,
                scale: 95,
                xOff: 15,
                desc: "no effect"
            }, {
                id: 20,
                name: "Devils Tail",
                price: 10000,
                scale: 95,
                xOff: 20,
                desc: "no effect"
            }, {
                id: 16,
                name: "Sawblade",
                price: 12000,
                scale: 90,
                spin: true,
                xOff: 0,
                desc: "deal damage to players that damage you",
                dmg: 0.15
            }, {
                id: 13,
                name: "Angel Wings",
                price: 15000,
                scale: 138,
                xOff: 22,
                desc: "slowly regenerates health over time",
                healthRegen: 3
            }, {
                id: 19,
                name: "Shadow Wings",
                price: 15000,
                scale: 138,
                xOff: 22,
                desc: "increased movement speed",
                spdMult: 1.1
            }, {
                id: 18,
                name: "Blood Wings",
                price: 20000,
                scale: 178,
                xOff: 26,
                desc: "restores health when you deal damage",
                healD: 0.2
            }, {
                id: 21,
                name: "Corrupt X Wings",
                price: 20000,
                scale: 178,
                xOff: 26,
                desc: "deal damage to players that damage you",
                dmg: 0.25
            }];


            /***/ }),

        /***/ "./src/js/libs/animText.js":
        /*!*********************************!*\
  !*** ./src/js/libs/animText.js ***!
  \*********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // ANIMATED TEXT:
            module.exports.AnimText = function() {

                // INIT:
                this.init = function(x, y, scale, speed, life, text, color) {
                    this.x = x;
                    this.y = y;
                    this.color = color;
                    this.scale = scale;
                    this.initialScale = this.scale + 15;
                    this.minScale = 30;
                    this.scaleSpeed = 0.7;
                    this.speed = speed * 0.5;
                    this.life = 1500;
                    this.text = text;

                };

                // UPDATE:
                this.update = function(delta) {
                    if (this.life) {
                        this.life -= delta;
                        this.y -= this.speed * delta;

                        this.scale *= 0.98;

                        if (this.scale < this.minScale) {
                            this.scale = this.minScale;
                            this.scaleSpeed *= -15;
                        } else if (this.scale >= this.startScale) {
                            this.scale = this.startScale;
                            this.scaleSpeed = 0;
                        }

                        if (this.life <= 0) {
                            this.life = 0;
                        }
                    }
                };

                // RENDER:
                this.render = function(ctxt, xOff, yOff) {
                    ctxt.strokeStyle = '#3d3f42';
                    ctxt.fillStyle = this.color;
                    ctxt.font = `${this.scale}px Hammersmith One`;
                    ctxt.strokeText(this.text, this.x - xOff, this.y - yOff);
                    ctxt.fillText(this.text, this.x - xOff, this.y - yOff);
                };

            }

            // TEXT MANAGER:
            module.exports.TextManager = function() {
                this.texts = [];

                // UPDATE:
                this.update = function(delta, ctxt, xOff, yOff) {
                    ctxt.textBaseline = "middle";
                    ctxt.textAlign = "center";
                    for (var i = 0; i < this.texts.length; ++i) {
                        if (this.texts[i].life) {
                            this.texts[i].update(delta);
                            this.texts[i].render(ctxt, xOff, yOff);
                        }
                    }
                };

                // SHOW TEXT:
                this.showText = function(x, y, scale, speed, life, text, color) {
                    var tmpText;
                    for (var i = 0; i < this.texts.length; ++i) {
                        if (!this.texts[i].life) {
                            tmpText = this.texts[i];
                            break;
                        }
                    }
                    if (!tmpText) {
                        tmpText = new module.exports.AnimText();
                        this.texts.push(tmpText);
                    }
                    tmpText.init(x, y, scale, speed, life, text, color);
                };
            }


            /***/ }),

        /***/ "./src/js/libs/io-client.js":
        /*!**********************************!*\
  !*** ./src/js/libs/io-client.js ***!
  \**********************************/
        /*! no static exports found */
        /***/ (function(module, exports, __webpack_require__) {

            let msgpack = required.msgpack;
            let config = __webpack_require__(/*! ../config */ "./src/js/config.js");

            module.exports = {
                socket: null,
                connected: false,
                socketId: -1,
                connect: function(address, callback, events) {
                    // if (this.socket) return;

                    // CREATE SOCKET:
                    var _this = this;
                    try {
                        var socketError = false;
                        var socketAddress = address;
                        this.pps = [];
                        this.socket = new WebSocket(socketAddress);
                        this.socket.binaryType = "arraybuffer";
                        this.socket.onmessage = function(message) {

                            // PARSE MESSAGE:
                            var data = new Uint8Array(message.data);
                            var parsed = msgpack.decode(data);
                            var type = parsed[0];
                            var data = parsed[1];

                            // CALL EVENT:
                            if (type == "io-init") {
                                _this.socketId = data[0];
                            } else {
                                events[type].apply(undefined, data);
                            }
                        };
                        this.socket.onopen = function() {
                            console.log("ws open!")
                            _this.connected = true;
                            callback();
                        };
                        this.socket.onclose = function(event) {
                            console.log("ws closed!", event.code)
                            _this.connected = false;
                            if (event.code == 4001) {
                                callback("Invalid Connection");
                            } else if (!socketError) {
                                callback("disconnected");
                            }
                        };
                        this.socket.onerror = function(error) {
                            console.log("ws error!")
                            if (this.socket && this.socket.readyState != WebSocket.OPEN) {
                                socketError = true;
                                console.error("Socket error", arguments);
                                callback("Socket error");
                            }
                        };
                    } catch(e) {
                        console.warn("Socket connection error:", e);
                        callback(e);
                    }
                },
                send: function(type) {
                    if(this.socket.readyState !== 1) return

                    /* replace item with new item */
                    const old = [
                        ["pp", "0"],
                        ["sp", "M"],
                        ["2", "D"],
                        ["7", "K"],
                        ["c", "d"],
                        ["5", "G"],
                        ["13c", "c"],
                        ["33", "a"],
                        ["6", "H"],
                        ["8", "L"],
                        ["9", "N"],
                        ["10", "b"],
                        ["rmd", "e"],
                        ["ch", "6"],
                        //  ["pp"]
                    ]

                    let index = 0, length = old.length;

                    while(index < length) {
                        let item = old[index];

                        if(item[0] === type) {
                            type = item[1];

                            break;
                        }

                        index += 1;
                    }

                    for(let packet of this.pps) {
                        const outdated = (Date.now() - packet >= 1e3);

                        if(outdated) this.pps.shift();
                    }

                    if(this.pps.length < 85) {
                        this.pps.push(Date.now());

                        this.original_send(...arguments);
                    }

                    document.updateInfoBar();
                },
                original_send: function(type) {
                    // EXTRACT DATA ARRAY:
                    var data = Array.prototype.slice.call(arguments, 1);

                    // SEND MESSAGE:
                    var binary = msgpack.encode([type, data]);

                    this.socket.send(binary);
                },
                socketReady: function() {
                    return (this.socket && this.connected);
                },
                close: function() {
                    this.socket && this.socket.close();
                }
            };


            /***/ }),

        /***/ "./src/js/libs/modernizr.js":
        /*!**********************************!*\
  !*** ./src/js/libs/modernizr.js ***!
  \**********************************/
        /*! no static exports found */
        /***/ (function(module, exports) {

            !function(e,n,s){function o(e,n){return typeof e===n}function a(){var e,n,s,a,t,f,l;for(var c in r)if(r.hasOwnProperty(c)){if(e=[],n=r[c],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(s=0;s<n.options.aliases.length;s++)e.push(n.options.aliases[s].toLowerCase());for(a=o(n.fn,"function")?n.fn():n.fn,t=0;t<e.length;t++)f=e[t],l=f.split("."),1===l.length?Modernizr[l[0]]=a:(!Modernizr[l[0]]||Modernizr[l[0]]instanceof Boolean||(Modernizr[l[0]]=new Boolean(Modernizr[l[0]])),Modernizr[l[0]][l[1]]=a),i.push((a?"":"no-")+l.join("-"))}}function t(e){var n=l.className,s=Modernizr._config.classPrefix||"";if(c&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+s+"no-js(\\s|$)");n=n.replace(o,"$1"+s+"js$2")}Modernizr._config.enableClasses&&(n+=" "+s+e.join(" "+s),c?l.className.baseVal=n:l.className=n)}var i=[],r=[],f={_version:"3.5.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var s=this;setTimeout(function(){n(s[e])},0)},addTest:function(e,n,s){r.push({name:e,fn:n,options:s})},addAsyncTest:function(e){r.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=f,Modernizr=new Modernizr;var l=n.documentElement,c="svg"===l.nodeName.toLowerCase();Modernizr.addTest("passiveeventlisteners",function(){var n=!1;try{var s=Object.defineProperty({},"passive",{get:function(){n=!0}});e.addEventListener("test",null,s)}catch(o){}return n}),a(),t(i),delete f.addTest,delete f.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();e.Modernizr=Modernizr}(window,document);


            /***/ }),

        /***/ "./src/js/libs/soundManager.js":
        /*!*************************************!*\
  !*** ./src/js/libs/soundManager.js ***!
  \*************************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // PLAYER MANAGER:
            module.exports.obj = function(config, UTILS) {

                // INIT:
                var tmpSound;
                this.sounds = [];
                this.active = true;

                // PLAY SOUND:
                this.play = function(id, volume, loop) {
                    if (!volume || !this.active) return;
                    tmpSound = this.sounds[id];
                    if (!tmpSound) {
                        tmpSound = new Howl({
                            src: ".././sound/" + id + ".mp3"
                        });
                        this.sounds[id] = tmpSound;
                    }
                    if (!loop || !tmpSound.isPlaying) {
                        tmpSound.isPlaying = true;
                        tmpSound.play();
                        tmpSound.volume((volume||1)*config.volumeMult);
                        tmpSound.loop(loop);
                    }
                };

                // TOGGLE MUTE:
                this.toggleMute = function(id, mute) {
                    tmpSound = this.sounds[id];
                    if (tmpSound) tmpSound.mute(mute);
                };

                // STOP SOUND:
                this.stop = function(id) {
                    tmpSound = this.sounds[id];
                    if (tmpSound) {
                        tmpSound.stop();
                        tmpSound.isPlaying = false;
                    }
                };

            };


            /***/ }),

        /***/ "./src/js/libs/utils.js":
        /*!******************************!*\
  !*** ./src/js/libs/utils.js ***!
  \******************************/
        /*! no static exports found */
        /***/ (function(module, exports) {


            // MATH UTILS:
            var mathABS = Math.abs;
            var mathCOS = Math.cos;
            var mathSIN = Math.sin;
            var mathPOW = Math.pow;
            var mathSQRT = Math.sqrt;
            var mathABS = Math.abs;
            var mathATAN2 = Math.atan2;
            var mathPI = Math.PI;

            // GLOBAL UTILS:
            module.exports.randInt = function (min, max) {
                return Math.floor(Math.random() * (max - min + 1)) + min;
            };
            module.exports.randFloat = function (min, max) {
                return Math.random() * (max - min + 1) + min;
            };
            module.exports.lerp = function (value1, value2, amount) {
                return value1 + (value2 - value1) * amount;
            };
            module.exports.decel = function (val, cel) {
                if (val > 0)
                    val = Math.max(0, val - cel);
                else if (val < 0)
                    val = Math.min(0, val + cel);
                return val;
            };
            module.exports.getDistance = function (x1, y1, x2, y2) {
                return mathSQRT((x2 -= x1) * x2 + (y2 -= y1) * y2);
            };
            module.exports.getDirection = function (x1, y1, x2, y2) {
                return mathATAN2(y1 - y2, x1 - x2);
            };
            module.exports.getAngleDist = function (a, b) {
                var p = mathABS(b - a) % (mathPI * 2);
                return (p > mathPI ? (mathPI * 2) - p : p);
            };
            module.exports.isNumber = function (n) {
                return (typeof n == "number" && !isNaN(n) && isFinite(n));
            };
            module.exports.isString = function (s) {
                return (s && typeof s == "string");
            };
            module.exports.kFormat = function (num) {
                return num > 999 ? (num / 1000).toFixed(1) + 'k' : num;
            };
            module.exports.capitalizeFirst = function (string) {
                return string.charAt(0).toUpperCase() + string.slice(1);
            };
            module.exports.fixTo = function (n, v) {
                return parseFloat(n.toFixed(v));
            };
            module.exports.sortByPoints = function (a, b) {
                return parseFloat(b.points) - parseFloat(a.points);
            };
            module.exports.lineInRect = function (recX, recY, recX2, recY2, x1, y1, x2, y2) {
                var minX = x1;
                var maxX = x2;
                if (x1 > x2) {
                    minX = x2;
                    maxX = x1;
                }
                if (maxX > recX2)
                    maxX = recX2;
                if (minX < recX)
                    minX = recX;
                if (minX > maxX)
                    return false;
                var minY = y1;
                var maxY = y2;
                var dx = x2 - x1;
                if (Math.abs(dx) > 0.0000001) {
                    var a = (y2 - y1) / dx;
                    var b = y1 - a * x1;
                    minY = a * minX + b;
                    maxY = a * maxX + b;
                }
                if (minY > maxY) {
                    var tmp = maxY;
                    maxY = minY;
                    minY = tmp;
                }
                if (maxY > recY2)
                    maxY = recY2;
                if (minY < recY)
                    minY = recY;
                if (minY > maxY)
                    return false;
                return true;
            };
            module.exports.containsPoint = function (element, x, y) {
                var bounds = element.getBoundingClientRect();
                var left = bounds.left + window.scrollX;
                var top = bounds.top + window.scrollY;
                var width = bounds.width;
                var height = bounds.height;

                var insideHorizontal = x > left && x < left + width;
                var insideVertical = y > top && y < top + height;
                return insideHorizontal && insideVertical;
            };
            module.exports.mousifyTouchEvent = function(event) {
                var touch = event.changedTouches[0];
                event.screenX = touch.screenX;
                event.screenY = touch.screenY;
                event.clientX = touch.clientX;
                event.clientY = touch.clientY;
                event.pageX = touch.pageX;
                event.pageY = touch.pageY;
            };
            module.exports.hookTouchEvents = function (element, skipPrevent) {
                var preventDefault = !skipPrevent;
                var isHovering = false;
                // var passive = window.Modernizr.passiveeventlisteners ? {passive: true} : false;
                var passive = false;
                element.addEventListener("touchstart", module.exports.checkTrusted(touchStart), passive);
                element.addEventListener("touchmove", module.exports.checkTrusted(touchMove), passive);
                element.addEventListener("touchend", module.exports.checkTrusted(touchEnd), passive);
                element.addEventListener("touchcancel", module.exports.checkTrusted(touchEnd), passive);
                element.addEventListener("touchleave", module.exports.checkTrusted(touchEnd), passive);
                function touchStart(e) {
                    module.exports.mousifyTouchEvent(e);
                    window.setUsingTouch(true);
                    if (preventDefault) {
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    if (element.onmouseover)
                        element.onmouseover(e);
                    isHovering = true;
                }
                function touchMove(e) {
                    module.exports.mousifyTouchEvent(e);
                    window.setUsingTouch(true);
                    if (preventDefault) {
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    if (module.exports.containsPoint(element, e.pageX, e.pageY)) {
                        if (!isHovering) {
                            if (element.onmouseover)
                                element.onmouseover(e);
                            isHovering = true;
                        }
                    } else {
                        if (isHovering) {
                            if (element.onmouseout)
                                element.onmouseout(e);
                            isHovering = false;
                        }
                    }
                }
                function touchEnd(e) {
                    module.exports.mousifyTouchEvent(e);
                    window.setUsingTouch(true);
                    if (preventDefault) {
                        e.preventDefault();
                        e.stopPropagation();
                    }
                    if (isHovering) {
                        if (element.onclick)
                            element.onclick(e);
                        if (element.onmouseout)
                            element.onmouseout(e);
                        isHovering = false;
                    }
                }
            };
            module.exports.removeAllChildren = function (element) {
                while (element.hasChildNodes()) {
                    element.removeChild(element.lastChild);
                }
            };
            module.exports.generateElement = function (config) {
                var element = document.createElement(config.tag || "div");
                function bind(configValue, elementValue) {
                    if (config[configValue])
                        element[elementValue] = config[configValue];
                }
                bind("text", "textContent");
                bind("html", "innerHTML");
                bind("class", "className");
                for (var key in config) {
                    switch (key) {
                        case "tag":
                        case "text":
                        case "html":
                        case "class":
                        case "style":
                        case "hookTouch":
                        case "parent":
                        case "children":
                            continue;
                        default:
                            break;
                    }
                    element[key] = config[key];
                }
                if (element.onclick)
                    element.onclick = module.exports.checkTrusted(element.onclick);
                if (element.onmouseover)
                    element.onmouseover = module.exports.checkTrusted(element.onmouseover);
                if (element.onmouseout)
                    element.onmouseout = module.exports.checkTrusted(element.onmouseout);
                if (config.style) {
                    element.style.cssText = config.style;
                }
                if (config.hookTouch) {
                    module.exports.hookTouchEvents(element);
                }
                if (config.parent) {
                    config.parent.appendChild(element);
                }
                if (config.children) {
                    for (var i = 0; i < config.children.length; i++) {
                        element.appendChild(config.children[i]);
                    }
                }
                return element;
            }
            module.exports.eventIsTrusted = function(ev) {
                if (ev && typeof ev.isTrusted == "boolean") {
                    return ev.isTrusted;
                } else {
                    return true;
                }
            }
            module.exports.checkTrusted = function(callback) {
                return function(ev) {
                    if (ev && ev instanceof Event && module.exports.eventIsTrusted(ev)) {
                        callback(ev);
                    } else {
                        //console.error("Event is not trusted.", ev);
                    }
                }
            }
            module.exports.randomString = function(length) {
                var text = "";
                var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
                for (var i = 0; i < length; i++) {
                    text += possible.charAt(Math.floor(Math.random() * possible.length));
                }
                return text;
            };
            module.exports.countInArray = function(array, val) {
                var count = 0;
                for (var i = 0; i < array.length; i++) {
                    if (array[i] === val) count++;
                } return count;
            };


            /***/ })

        /******/ });
    //# sourceMappingURL=bundle.js.map
})();