code:
//=============================================================================
// rpg_core.js v1.6.1
//=============================================================================

//-----------------------------------------------------------------------------
/**
 * This is not a class, but contains some methods that will be added to the
 * standard Javascript objects.
 *
 * @class JsExtensions
 */
function JsExtensions() {
  throw new Error("This is not a class");
}

/**
 * Returns a number whose value is limited to the given range.
 *
 * @method Number.prototype.clamp
 * @param {Number} min The lower boundary
 * @param {Number} max The upper boundary
 * @return {Number} A number in the range (min, max)
 */
Number.prototype.clamp = function (min, max) {
  return Math.min(Math.max(this, min), max);
};

/**
 * Returns a modulo value which is always positive.
 *
 * @method Number.prototype.mod
 * @param {Number} n The divisor
 * @return {Number} A modulo value
 */
Number.prototype.mod = function (n) {
  return ((this % n) + n) % n;
};

/**
 * Replaces %1, %2 and so on in the string to the arguments.
 *
 * @method String.prototype.format
 * @param {Any} ...args The objects to format
 * @return {String} A formatted string
 */
String.prototype.format = function () {
  var args = arguments;
  return this.replace(/%([0-9]+)/g, function (s, n) {
    return args[Number(n) - 1];
  });
};

/**
 * Makes a number string with leading zeros.
 *
 * @method String.prototype.padZero
 * @param {Number} length The length of the output string
 * @return {String} A string with leading zeros
 */
String.prototype.padZero = function (length) {
  var s = this;
  while (s.length < length) {
    s = "0" + s;
  }
  return s;
};

/**
 * Makes a number string with leading zeros.
 *
 * @method Number.prototype.padZero
 * @param {Number} length The length of the output string
 * @return {String} A string with leading zeros
 */
Number.prototype.padZero = function (length) {
  return String(this).padZero(length);
};

Object.defineProperties(Array.prototype, {
  /**
   * Checks whether the two arrays are same.
   *
   * @method Array.prototype.equals
   * @param {Array} array The array to compare to
   * @return {Boolean} True if the two arrays are same
   */
  equals: {
    enumerable: false,
    value: function (array) {
      if (!array || this.length !== array.length) {
        return false;
      }
      for (var i = 0; i < this.length; i++) {
        if (this[i] instanceof Array && array[i] instanceof Array) {
          if (!this[i].equals(array[i])) {
            return false;
          }
        } else if (this[i] !== array[i]) {
          return false;
        }
      }
      return true;
    },
  },
  /**
   * Makes a shallow copy of the array.
   *
   * @method Array.prototype.clone
   * @return {Array} A shallow copy of the array
   */
  clone: {
    enumerable: false,
    value: function () {
      return this.slice(0);
    },
  },
  /**
   * Checks whether the array contains a given element.
   *
   * @method Array.prototype.contains
   * @param {Any} element The element to search for
   * @return {Boolean} True if the array contains a given element
   */
  contains: {
    enumerable: false,
    value: function (element) {
      return this.indexOf(element) >= 0;
    },
  },
});

/**
 * Checks whether the string contains a given string.
 *
 * @method String.prototype.contains
 * @param {String} string The string to search for
 * @return {Boolean} True if the string contains a given string
 */
String.prototype.contains = function (string) {
  return this.indexOf(string) >= 0;
};

/**
 * Generates a random integer in the range (0, max-1).
 *
 * @static
 * @method Math.randomInt
 * @param {Number} max The upper boundary (excluded)
 * @return {Number} A random integer
 */
Math.randomInt = function (max) {
  return Math.floor(max * Math.random());
};

//-----------------------------------------------------------------------------
/**
 * The static class that defines utility methods.
 *
 * @class Utils
 */
function Utils() {
  throw new Error("This is a static class");
}

/**
 * The name of the RPG Maker. 'MV' in the current version.
 *
 * @static
 * @property RPGMAKER_NAME
 * @type String
 * @final
 */
Utils.RPGMAKER_NAME = "MV";

/**
 * The version of the RPG Maker.
 *
 * @static
 * @property RPGMAKER_VERSION
 * @type String
 * @final
 */
Utils.RPGMAKER_VERSION = "1.6.1";

/**
 * Checks whether the option is in the query string.
 *
 * @static
 * @method isOptionValid
 * @param {String} name The option name
 * @return {Boolean} True if the option is in the query string
 */
Utils.isOptionValid = function (name) {
  if (location.search.slice(1).split("&").contains(name)) {
    return 1;
  }
  if (
    typeof nw !== "undefined" &&
    nw.App.argv.length > 0 &&
    nw.App.argv[0].split("&").contains(name)
  ) {
    return 1;
  }
  return 0;
};

/**
 * Checks whether the platform is NW.js.
 *
 * @static
 * @method isNwjs
 * @return {Boolean} True if the platform is NW.js
 */
Utils.isNwjs = function () {
  return typeof require === "function" && typeof process === "object";
};

/**
 * Checks whether the platform is a mobile device.
 *
 * @static
 * @method isMobileDevice
 * @return {Boolean} True if the platform is a mobile device
 */
Utils.isMobileDevice = function () {
  var r = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
  return !!navigator.userAgent.match(r);
};

/**
 * Checks whether the browser is Mobile Safari.
 *
 * @static
 * @method isMobileSafari
 * @return {Boolean} True if the browser is Mobile Safari
 */
Utils.isMobileSafari = function () {
  var agent = navigator.userAgent;
  return !!(
    agent.match(/iPhone|iPad|iPod/) &&
    agent.match(/AppleWebKit/) &&
    !agent.match("CriOS")
  );
};

/**
 * Checks whether the browser is Android Chrome.
 *
 * @static
 * @method isAndroidChrome
 * @return {Boolean} True if the browser is Android Chrome
 */
Utils.isAndroidChrome = function () {
  var agent = navigator.userAgent;
  return !!(agent.match(/Android/) && agent.match(/Chrome/));
};

/**
 * Checks whether the browser can read files in the game folder.
 *
 * @static
 * @method canReadGameFiles
 * @return {Boolean} True if the browser can read files in the game folder
 */
Utils.canReadGameFiles = function () {
  var scripts = document.getElementsByTagName("script");
  var lastScript = scripts[scripts.length - 1];
  var xhr = new XMLHttpRequest();
  try {
    xhr.open("GET", lastScript.src);
    xhr.overrideMimeType("text/javascript");
    xhr.send();
    return true;
  } catch (e) {
    return false;
  }
};

/**
 * Makes a CSS color string from RGB values.
 *
 * @static
 * @method rgbToCssColor
 * @param {Number} r The red value in the range (0, 255)
 * @param {Number} g The green value in the range (0, 255)
 * @param {Number} b The blue value in the range (0, 255)
 * @return {String} CSS color string
 */
Utils.rgbToCssColor = function (r, g, b) {
  r = Math.round(r);
  g = Math.round(g);
  b = Math.round(b);
  return "rgb(" + r + "," + g + "," + b + ")";
};

Utils._id = 1;
Utils.generateRuntimeId = function () {
  return Utils._id++;
};

Utils._supportPassiveEvent = null;
/**
 * Test this browser support passive event feature
 *
 * @static
 * @method isSupportPassiveEvent
 * @return {Boolean} this browser support passive event or not
 */
Utils.isSupportPassiveEvent = function () {
  if (typeof Utils._supportPassiveEvent === "boolean") {
    return Utils._supportPassiveEvent;
  }
  // test support passive event
  // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
  var passive = false;
  var options = Object.defineProperty({}, "passive", {
    get: function () {
      passive = true;
    },
  });
  window.addEventListener("test", null, options);
  Utils._supportPassiveEvent = passive;
  return passive;
};

//-----------------------------------------------------------------------------
/**
 * The resource class. Allows to be collected as a garbage if not use for some time or ticks
 *
 * @class CacheEntry
 * @constructor
 * @param {ResourceManager} resource manager
 * @param {string} key, url of the resource
 * @param {string} item - Bitmap, HTML5Audio, WebAudio - whatever you want to store in the cache
 */
function CacheEntry(cache, key, item) {
  this.cache = cache;
  this.key = key;
  this.item = item;
  this.cached = false;
  this.touchTicks = 0;
  this.touchSeconds = 0;
  this.ttlTicks = 0;
  this.ttlSeconds = 0;
  this.freedByTTL = false;
}

/**
 * frees the resource
 */
CacheEntry.prototype.free = function (byTTL) {
  this.freedByTTL = byTTL || false;
  if (this.cached) {
    this.cached = false;
    delete this.cache._inner[this.key];
  }
};

/**
 * Allocates the resource
 * @returns {CacheEntry}
 */
CacheEntry.prototype.allocate = function () {
  if (!this.cached) {
    this.cache._inner[this.key] = this;
    this.cached = true;
  }
  this.touch();
  return this;
};

/**
 * Sets the time to live
 * @param {number} ticks TTL in ticks, 0 if not set
 * @param {number} time TTL in seconds, 0 if not set
 * @returns {CacheEntry}
 */
CacheEntry.prototype.setTimeToLive = function (ticks, seconds) {
  this.ttlTicks = ticks || 0;
  this.ttlSeconds = seconds || 0;
  return this;
};

CacheEntry.prototype.isStillAlive = function () {
  var cache = this.cache;
  return (
    (this.ttlTicks == 0 ||
      this.touchTicks + this.ttlTicks < cache.updateTicks) &&
    (this.ttlSeconds == 0 ||
      this.touchSeconds + this.ttlSeconds < cache.updateSeconds)
  );
};

/**
 * makes sure that resource wont freed by Time To Live
 * if resource was already freed by TTL, put it in cache again
 */
CacheEntry.prototype.touch = function () {
  var cache = this.cache;
  if (this.cached) {
    this.touchTicks = cache.updateTicks;
    this.touchSeconds = cache.updateSeconds;
  } else if (this.freedByTTL) {
    this.freedByTTL = false;
    if (!cache._inner[this.key]) {
      cache._inner[this.key] = this;
    }
  }
};

/**
 * Cache for images, audio, or any other kind of resource
 * @param manager
 * @constructor
 */
function CacheMap(manager) {
  this.manager = manager;
  this._inner = {};
  this._lastRemovedEntries = {};
  this.updateTicks = 0;
  this.lastCheckTTL = 0;
  this.delayCheckTTL = 100.0;
  this.updateSeconds = Date.now();
}

/**
 * checks ttl of all elements and removes dead ones
 */
CacheMap.prototype.checkTTL = function () {
  var cache = this._inner;
  var temp = this._lastRemovedEntries;
  if (!temp) {
    temp = [];
    this._lastRemovedEntries = temp;
  }
  for (var key in cache) {
    var entry = cache[key];
    if (!entry.isStillAlive()) {
      temp.push(entry);
    }
  }
  for (var i = 0; i < temp.length; i++) {
    temp[i].free(true);
  }
  temp.length = 0;
};

/**
 * cache item
 * @param key url of cache element
 * @returns {*|null}
 */
CacheMap.prototype.getItem = function (key) {
  var entry = this._inner[key];
  if (entry) {
    return entry.item;
  }
  return null;
};

CacheMap.prototype.clear = function () {
  var keys = Object.keys(this._inner);
  for (var i = 0; i < keys.length; i++) {
    this._inner[keys[i]].free();
  }
};

CacheMap.prototype.setItem = function (key, item) {
  return new CacheEntry(this, key, item).allocate();
};

CacheMap.prototype.update = function (ticks, delta) {
  this.updateTicks += ticks;
  this.updateSeconds += delta;
  if (this.updateSeconds >= this.delayCheckTTL + this.lastCheckTTL) {
    this.lastCheckTTL = this.updateSeconds;
    this.checkTTL();
  }
};

function ImageCache() {
  this.initialize.apply(this, arguments);
}

ImageCache.limit = 10 * 1000 * 1000;

ImageCache.prototype.initialize = function () {
  this._items = {};
};

ImageCache.prototype.add = function (key, value) {
  this._items[key] = {
    bitmap: value,
    touch: Date.now(),
    key: key,
  };

  this._truncateCache();
};

ImageCache.prototype.get = function (key) {
  if (this._items[key]) {
    var item = this._items[key];
    item.touch = Date.now();
    return item.bitmap;
  }

  return null;
};

ImageCache.prototype.reserve = function (key, value, reservationId) {
  if (!this._items[key]) {
    this._items[key] = {
      bitmap: value,
      touch: Date.now(),
      key: key,
    };
  }

  this._items[key].reservationId = reservationId;
};

ImageCache.prototype.releaseReservation = function (reservationId) {
  var items = this._items;

  Object.keys(items)
    .map(function (key) {
      return items[key];
    })
    .forEach(function (item) {
      if (item.reservationId === reservationId) {
        delete item.reservationId;
      }
    });
};

ImageCache.prototype._truncateCache = function () {
  var items = this._items;
  var sizeLeft = ImageCache.limit;

  Object.keys(items)
    .map(function (key) {
      return items[key];
    })
    .sort(function (a, b) {
      return b.touch - a.touch;
    })
    .forEach(
      function (item) {
        if (sizeLeft > 0 || this._mustBeHeld(item)) {
          var bitmap = item.bitmap;
          sizeLeft -= bitmap.width * bitmap.height;
        } else {
          delete items[item.key];
        }
      }.bind(this)
    );
};

ImageCache.prototype._mustBeHeld = function (item) {
  // request only is weak so It's purgeable
  if (item.bitmap.isRequestOnly()) return false;
  // reserved item must be held
  if (item.reservationId) return true;
  // not ready bitmap must be held (because of checking isReady())
  if (!item.bitmap.isReady()) return true;
  // then the item may purgeable
  return false;
};

ImageCache.prototype.isReady = function () {
  var items = this._items;
  return !Object.keys(items).some(function (key) {
    return !items[key].bitmap.isRequestOnly() && !items[key].bitmap.isReady();
  });
};

ImageCache.prototype.getErrorBitmap = function () {
  var items = this._items;
  var bitmap = null;
  if (
    Object.keys(items).some(function (key) {
      if (items[key].bitmap.isError()) {
        bitmap = items[key].bitmap;
        return true;
      }
      return false;
    })
  ) {
    return bitmap;
  }

  return null;
};
function RequestQueue() {
  this.initialize.apply(this, arguments);
}

RequestQueue.prototype.initialize = function () {
  this._queue = [];
};

RequestQueue.prototype.enqueue = function (key, value) {
  this._queue.push({
    key: key,
    value: value,
  });
};

RequestQueue.prototype.update = function () {
  if (this._queue.length === 0) return;

  var top = this._queue[0];
  if (top.value.isRequestReady()) {
    this._queue.shift();
    if (this._queue.length !== 0) {
      this._queue[0].value.startRequest();
    }
  } else {
    top.value.startRequest();
  }
};

RequestQueue.prototype.raisePriority = function (key) {
  for (var n = 0; n < this._queue.length; n++) {
    var item = this._queue[n];
    if (item.key === key) {
      this._queue.splice(n, 1);
      this._queue.unshift(item);
      break;
    }
  }
};

RequestQueue.prototype.clear = function () {
  this._queue.splice(0);
};
//-----------------------------------------------------------------------------
/**
 * The point class.
 *
 * @class Point
 * @constructor
 * @param {Number} x The x coordinate
 * @param {Number} y The y coordinate
 */
function Point() {
  this.initialize.apply(this, arguments);
}

Point.prototype = Object.create(PIXI.Point.prototype);
Point.prototype.constructor = Point;

Point.prototype.initialize = function (x, y) {
  PIXI.Point.call(this, x, y);
};

/**
 * The x coordinate.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate.
 *
 * @property y
 * @type Number
 */

//-----------------------------------------------------------------------------
/**
 * The rectangle class.
 *
 * @class Rectangle
 * @constructor
 * @param {Number} x The x coordinate for the upper-left corner
 * @param {Number} y The y coordinate for the upper-left corner
 * @param {Number} width The width of the rectangle
 * @param {Number} height The height of the rectangle
 */
function Rectangle() {
  this.initialize.apply(this, arguments);
}

Rectangle.prototype = Object.create(PIXI.Rectangle.prototype);
Rectangle.prototype.constructor = Rectangle;

Rectangle.prototype.initialize = function (x, y, width, height) {
  PIXI.Rectangle.call(this, x, y, width, height);
};

/**
 * @static
 * @property emptyRectangle
 * @type Rectangle
 * @private
 */
Rectangle.emptyRectangle = new Rectangle(0, 0, 0, 0);

/**
 * The x coordinate for the upper-left corner.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate for the upper-left corner.
 *
 * @property y
 * @type Number
 */

/**
 * The width of the rectangle.
 *
 * @property width
 * @type Number
 */

/**
 * The height of the rectangle.
 *
 * @property height
 * @type Number
 */

//-----------------------------------------------------------------------------
/**
 * The basic object that represents an image.
 *
 * @class Bitmap
 * @constructor
 * @param {Number} width The width of the bitmap
 * @param {Number} height The height of the bitmap
 */
function Bitmap() {
  this.initialize.apply(this, arguments);
}

//for iOS. img consumes memory. so reuse it.
Bitmap._reuseImages = [];

/**
 * Bitmap states(Bitmap._loadingState):
 *
 * none:
 * Empty Bitmap
 *
 * pending:
 * Url requested, but pending to load until startRequest called
 *
 * purged:
 * Url request completed and purged.
 *
 * requesting:
 * Requesting supplied URI now.
 *
 * requestCompleted:
 * Request completed
 *
 * decrypting:
 * requesting encrypted data from supplied URI or decrypting it.
 *
 * decryptCompleted:
 * Decrypt completed
 *
 * loaded:
 * loaded. isReady() === true, so It's usable.
 *
 * error:
 * error occurred
 *
 */

Bitmap.prototype._createCanvas = function (width, height) {
  this.__canvas = this.__canvas || document.createElement("canvas");
  this.__context = this.__canvas.getContext("2d");

  this.__canvas.width = Math.max(width || 0, 1);
  this.__canvas.height = Math.max(height || 0, 1);

  if (this._image) {
    var w = Math.max(this._image.width || 0, 1);
    var h = Math.max(this._image.height || 0, 1);
    this.__canvas.width = w;
    this.__canvas.height = h;
    this._createBaseTexture(this._canvas);

    this.__context.drawImage(this._image, 0, 0);
  }

  this._setDirty();
};

Bitmap.prototype._createBaseTexture = function (source) {
  this.__baseTexture = new PIXI.BaseTexture(source);
  this.__baseTexture.mipmap = false;
  this.__baseTexture.width = source.width;
  this.__baseTexture.height = source.height;

  if (this._smooth) {
    this._baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
  } else {
    this._baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
  }
};

Bitmap.prototype._clearImgInstance = function () {
  this._image.src = "";
  this._image.onload = null;
  this._image.onerror = null;
  this._errorListener = null;
  this._loadListener = null;

  Bitmap._reuseImages.push(this._image);
  this._image = null;
};

//
//We don't want to waste memory, so creating canvas is deferred.
//
Object.defineProperties(Bitmap.prototype, {
  _canvas: {
    get: function () {
      if (!this.__canvas) this._createCanvas();
      return this.__canvas;
    },
  },
  _context: {
    get: function () {
      if (!this.__context) this._createCanvas();
      return this.__context;
    },
  },

  _baseTexture: {
    get: function () {
      if (!this.__baseTexture)
        this._createBaseTexture(this._image || this.__canvas);
      return this.__baseTexture;
    },
  },
});

Bitmap.prototype._renewCanvas = function () {
  var newImage = this._image;
  if (
    newImage &&
    this.__canvas &&
    (this.__canvas.width < newImage.width ||
      this.__canvas.height < newImage.height)
  ) {
    this._createCanvas();
  }
};

Bitmap.prototype.initialize = function (width, height) {
  if (!this._defer) {
    this._createCanvas(width, height);
  }

  this._image = null;
  this._url = "";
  this._paintOpacity = 255;
  this._smooth = false;
  this._loadListeners = [];
  this._loadingState = "none";
  this._decodeAfterRequest = false;

  /**
   * Cache entry, for images. In all cases _url is the same as cacheEntry.key
   * @type CacheEntry
   */
  this.cacheEntry = null;

  /**
   * The face name of the font.
   *
   * @property fontFace
   * @type String
   */
  this.fontFace = "GameFont";

  /**
   * The size of the font in pixels.
   *
   * @property fontSize
   * @type Number
   */
  this.fontSize = 28;

  /**
   * Whether the font is italic.
   *
   * @property fontItalic
   * @type Boolean
   */
  this.fontItalic = false;

  /**
   * The color of the text in CSS format.
   *
   * @property textColor
   * @type String
   */
  this.textColor = "#ffffff";

  /**
   * The color of the outline of the text in CSS format.
   *
   * @property outlineColor
   * @type String
   */
  this.outlineColor = "rgba(0, 0, 0, 0.5)";

  /**
   * The width of the outline of the text.
   *
   * @property outlineWidth
   * @type Number
   */
  this.outlineWidth = 4;
};

/**
 * Loads a image file and returns a new bitmap object.
 *
 * @static
 * @method load
 * @param {String} url The image url of the texture
 * @return Bitmap
 */
Bitmap.load = function (url) {
  var bitmap = Object.create(Bitmap.prototype);
  bitmap._defer = true;
  bitmap.initialize();

  bitmap._decodeAfterRequest = true;
  bitmap._requestImage(url);

  return bitmap;
};

/**
 * Takes a snapshot of the game screen and returns a new bitmap object.
 *
 * @static
 * @method snap
 * @param {Stage} stage The stage object
 * @return Bitmap
 */
Bitmap.snap = function (stage) {
  var width = Graphics.width;
  var height = Graphics.height;
  var bitmap = new Bitmap(width, height);
  var context = bitmap._context;
  var renderTexture = PIXI.RenderTexture.create(width, height);
  if (stage) {
    Graphics._renderer.render(stage, renderTexture);
    stage.worldTransform.identity();
    var canvas = null;
    if (Graphics.isWebGL()) {
      canvas = Graphics._renderer.extract.canvas(renderTexture);
    } else {
      canvas = renderTexture.baseTexture._canvasRenderTarget.canvas;
    }
    context.drawImage(canvas, 0, 0);
  } else {
  }
  renderTexture.destroy({ destroyBase: true });
  bitmap._setDirty();
  return bitmap;
};

/**
 * Checks whether the bitmap is ready to render.
 *
 * @method isReady
 * @return {Boolean} True if the bitmap is ready to render
 */
Bitmap.prototype.isReady = function () {
  return this._loadingState === "loaded" || this._loadingState === "none";
};

/**
 * Checks whether a loading error has occurred.
 *
 * @method isError
 * @return {Boolean} True if a loading error has occurred
 */
Bitmap.prototype.isError = function () {
  return this._loadingState === "error";
};

/**
 * touch the resource
 * @method touch
 */
Bitmap.prototype.touch = function () {
  if (this.cacheEntry) {
    this.cacheEntry.touch();
  }
};

/**
 * [read-only] The url of the image file.
 *
 * @property url
 * @type String
 */
Object.defineProperty(Bitmap.prototype, "url", {
  get: function () {
    return this._url;
  },
  configurable: true,
});

/**
 * [read-only] The base texture that holds the image.
 *
 * @property baseTexture
 * @type PIXI.BaseTexture
 */
Object.defineProperty(Bitmap.prototype, "baseTexture", {
  get: function () {
    return this._baseTexture;
  },
  configurable: true,
});

/**
 * [read-only] The bitmap canvas.
 *
 * @property canvas
 * @type HTMLCanvasElement
 */
Object.defineProperty(Bitmap.prototype, "canvas", {
  get: function () {
    return this._canvas;
  },
  configurable: true,
});

/**
 * [read-only] The 2d context of the bitmap canvas.
 *
 * @property context
 * @type CanvasRenderingContext2D
 */
Object.defineProperty(Bitmap.prototype, "context", {
  get: function () {
    return this._context;
  },
  configurable: true,
});

/**
 * [read-only] The width of the bitmap.
 *
 * @property width
 * @type Number
 */
Object.defineProperty(Bitmap.prototype, "width", {
  get: function () {
    if (this.isReady()) {
      return this._image ? this._image.width : this._canvas.width;
    }

    return 0;
  },
  configurable: true,
});

/**
 * [read-only] The height of the bitmap.
 *
 * @property height
 * @type Number
 */
Object.defineProperty(Bitmap.prototype, "height", {
  get: function () {
    if (this.isReady()) {
      return this._image ? this._image.height : this._canvas.height;
    }

    return 0;
  },
  configurable: true,
});

/**
 * [read-only] The rectangle of the bitmap.
 *
 * @property rect
 * @type Rectangle
 */
Object.defineProperty(Bitmap.prototype, "rect", {
  get: function () {
    return new Rectangle(0, 0, this.width, this.height);
  },
  configurable: true,
});

/**
 * Whether the smooth scaling is applied.
 *
 * @property smooth
 * @type Boolean
 */
Object.defineProperty(Bitmap.prototype, "smooth", {
  get: function () {
    return this._smooth;
  },
  set: function (value) {
    if (this._smooth !== value) {
      this._smooth = value;
      if (this.__baseTexture) {
        if (this._smooth) {
          this._baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
        } else {
          this._baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
        }
      }
    }
  },
  configurable: true,
});

/**
 * The opacity of the drawing object in the range (0, 255).
 *
 * @property paintOpacity
 * @type Number
 */
Object.defineProperty(Bitmap.prototype, "paintOpacity", {
  get: function () {
    return this._paintOpacity;
  },
  set: function (value) {
    if (this._paintOpacity !== value) {
      this._paintOpacity = value;
      this._context.globalAlpha = this._paintOpacity / 255;
    }
  },
  configurable: true,
});

/**
 * Resizes the bitmap.
 *
 * @method resize
 * @param {Number} width The new width of the bitmap
 * @param {Number} height The new height of the bitmap
 */
Bitmap.prototype.resize = function (width, height) {
  width = Math.max(width || 0, 1);
  height = Math.max(height || 0, 1);
  this._canvas.width = width;
  this._canvas.height = height;
  this._baseTexture.width = width;
  this._baseTexture.height = height;
};

/**
 * Performs a block transfer.
 *
 * @method blt
 * @param {Bitmap} source The bitmap to draw
 * @param {Number} sx The x coordinate in the source
 * @param {Number} sy The y coordinate in the source
 * @param {Number} sw The width of the source image
 * @param {Number} sh The height of the source image
 * @param {Number} dx The x coordinate in the destination
 * @param {Number} dy The y coordinate in the destination
 * @param {Number} [dw=sw] The width to draw the image in the destination
 * @param {Number} [dh=sh] The height to draw the image in the destination
 */
Bitmap.prototype.blt = function (source, sx, sy, sw, sh, dx, dy, dw, dh) {
  dw = dw || sw;
  dh = dh || sh;
  if (
    sx >= 0 &&
    sy >= 0 &&
    sw > 0 &&
    sh > 0 &&
    dw > 0 &&
    dh > 0 &&
    sx + sw <= source.width &&
    sy + sh <= source.height
  ) {
    this._context.globalCompositeOperation = "source-over";
    this._context.drawImage(source._canvas, sx, sy, sw, sh, dx, dy, dw, dh);
    this._setDirty();
  }
};

/**
 * Performs a block transfer, using assumption that original image was not modified (no hue)
 *
 * @method blt
 * @param {Bitmap} source The bitmap to draw
 * @param {Number} sx The x coordinate in the source
 * @param {Number} sy The y coordinate in the source
 * @param {Number} sw The width of the source image
 * @param {Number} sh The height of the source image
 * @param {Number} dx The x coordinate in the destination
 * @param {Number} dy The y coordinate in the destination
 * @param {Number} [dw=sw] The width to draw the image in the destination
 * @param {Number} [dh=sh] The height to draw the image in the destination
 */
Bitmap.prototype.bltImage = function (source, sx, sy, sw, sh, dx, dy, dw, dh) {
  dw = dw || sw;
  dh = dh || sh;
  if (
    sx >= 0 &&
    sy >= 0 &&
    sw > 0 &&
    sh > 0 &&
    dw > 0 &&
    dh > 0 &&
    sx + sw <= source.width &&
    sy + sh <= source.height
  ) {
    this._context.globalCompositeOperation = "source-over";
    this._context.drawImage(source._image, sx, sy, sw, sh, dx, dy, dw, dh);
    this._setDirty();
  }
};

/**
 * Returns pixel color at the specified point.
 *
 * @method getPixel
 * @param {Number} x The x coordinate of the pixel in the bitmap
 * @param {Number} y The y coordinate of the pixel in the bitmap
 * @return {String} The pixel color (hex format)
 */
Bitmap.prototype.getPixel = function (x, y) {
  var data = this._context.getImageData(x, y, 1, 1).data;
  var result = "#";
  for (var i = 0; i < 3; i++) {
    result += data[i].toString(16).padZero(2);
  }
  return result;
};

/**
 * Returns alpha pixel value at the specified point.
 *
 * @method getAlphaPixel
 * @param {Number} x The x coordinate of the pixel in the bitmap
 * @param {Number} y The y coordinate of the pixel in the bitmap
 * @return {String} The alpha value
 */
Bitmap.prototype.getAlphaPixel = function (x, y) {
  var data = this._context.getImageData(x, y, 1, 1).data;
  return data[3];
};

/**
 * Clears the specified rectangle.
 *
 * @method clearRect
 * @param {Number} x The x coordinate for the upper-left corner
 * @param {Number} y The y coordinate for the upper-left corner
 * @param {Number} width The width of the rectangle to clear
 * @param {Number} height The height of the rectangle to clear
 */
Bitmap.prototype.clearRect = function (x, y, width, height) {
  this._context.clearRect(x, y, width, height);
  this._setDirty();
};

/**
 * Clears the entire bitmap.
 *
 * @method clear
 */
Bitmap.prototype.clear = function () {
  this.clearRect(0, 0, this.width, this.height);
};

/**
 * Fills the specified rectangle.
 *
 * @method fillRect
 * @param {Number} x The x coordinate for the upper-left corner
 * @param {Number} y The y coordinate for the upper-left corner
 * @param {Number} width The width of the rectangle to fill
 * @param {Number} height The height of the rectangle to fill
 * @param {String} color The color of the rectangle in CSS format
 */
Bitmap.prototype.fillRect = function (x, y, width, height, color) {
  var context = this._context;
  context.save();
  context.fillStyle = color;
  context.fillRect(x, y, width, height);
  context.restore();
  this._setDirty();
};

/**
 * Fills the entire bitmap.
 *
 * @method fillAll
 * @param {String} color The color of the rectangle in CSS format
 */
Bitmap.prototype.fillAll = function (color) {
  this.fillRect(0, 0, this.width, this.height, color);
};

/**
 * Draws the rectangle with a gradation.
 *
 * @method gradientFillRect
 * @param {Number} x The x coordinate for the upper-left corner
 * @param {Number} y The y coordinate for the upper-left corner
 * @param {Number} width The width of the rectangle to fill
 * @param {Number} height The height of the rectangle to fill
 * @param {String} color1 The gradient starting color
 * @param {String} color2 The gradient ending color
 * @param {Boolean} vertical Wether the gradient should be draw as vertical or not
 */
Bitmap.prototype.gradientFillRect = function (
  x,
  y,
  width,
  height,
  color1,
  color2,
  vertical
) {
  var context = this._context;
  var grad;
  if (vertical) {
    grad = context.createLinearGradient(x, y, x, y + height);
  } else {
    grad = context.createLinearGradient(x, y, x + width, y);
  }
  grad.addColorStop(0, color1);
  grad.addColorStop(1, color2);
  context.save();
  context.fillStyle = grad;
  context.fillRect(x, y, width, height);
  context.restore();
  this._setDirty();
};

/**
 * Draw a bitmap in the shape of a circle
 *
 * @method drawCircle
 * @param {Number} x The x coordinate based on the circle center
 * @param {Number} y The y coordinate based on the circle center
 * @param {Number} radius The radius of the circle
 * @param {String} color The color of the circle in CSS format
 */
Bitmap.prototype.drawCircle = function (x, y, radius, color) {
  var context = this._context;
  context.save();
  context.fillStyle = color;
  context.beginPath();
  context.arc(x, y, radius, 0, Math.PI * 2, false);
  context.fill();
  context.restore();
  this._setDirty();
};

/**
 * Draws the outline text to the bitmap.
 *
 * @method drawText
 * @param {String} text The text that will be drawn
 * @param {Number} x The x coordinate for the left of the text
 * @param {Number} y The y coordinate for the top of the text
 * @param {Number} maxWidth The maximum allowed width of the text
 * @param {Number} lineHeight The height of the text line
 * @param {String} align The alignment of the text
 */
Bitmap.prototype.drawText = function (text, x, y, maxWidth, lineHeight, align) {
  // Note: Firefox has a bug with textBaseline: Bug 737852
  //       So we use 'alphabetic' here.
  if (text !== undefined) {
    var tx = x;
    var ty = y + lineHeight - (lineHeight - this.fontSize * 0.7) / 2;
    var context = this._context;
    var alpha = context.globalAlpha;
    maxWidth = maxWidth || 0xffffffff;
    if (align === "center") {
      tx += maxWidth / 2;
    }
    if (align === "right") {
      tx += maxWidth;
    }
    context.save();
    context.font = this._makeFontNameText();
    context.textAlign = align;
    context.textBaseline = "alphabetic";
    context.globalAlpha = 1;
    this._drawTextOutline(text, tx, ty, maxWidth);
    context.globalAlpha = alpha;
    this._drawTextBody(text, tx, ty, maxWidth);
    context.restore();
    this._setDirty();
  }
};

/**
 * Returns the width of the specified text.
 *
 * @method measureTextWidth
 * @param {String} text The text to be measured
 * @return {Number} The width of the text in pixels
 */
Bitmap.prototype.measureTextWidth = function (text) {
  var context = this._context;
  context.save();
  context.font = this._makeFontNameText();
  var width = context.measureText(text).width;
  context.restore();
  return width;
};

/**
 * Changes the color tone of the entire bitmap.
 *
 * @method adjustTone
 * @param {Number} r The red strength in the range (-255, 255)
 * @param {Number} g The green strength in the range (-255, 255)
 * @param {Number} b The blue strength in the range (-255, 255)
 */
Bitmap.prototype.adjustTone = function (r, g, b) {
  if ((r || g || b) && this.width > 0 && this.height > 0) {
    var context = this._context;
    var imageData = context.getImageData(0, 0, this.width, this.height);
    var pixels = imageData.data;
    for (var i = 0; i < pixels.length; i += 4) {
      pixels[i + 0] += r;
      pixels[i + 1] += g;
      pixels[i + 2] += b;
    }
    context.putImageData(imageData, 0, 0);
    this._setDirty();
  }
};

/**
 * Rotates the hue of the entire bitmap.
 *
 * @method rotateHue
 * @param {Number} offset The hue offset in 360 degrees
 */
Bitmap.prototype.rotateHue = function (offset) {
  function rgbToHsl(r, g, b) {
    var cmin = Math.min(r, g, b);
    var cmax = Math.max(r, g, b);
    var h = 0;
    var s = 0;
    var l = (cmin + cmax) / 2;
    var delta = cmax - cmin;

    if (delta > 0) {
      if (r === cmax) {
        h = 60 * (((g - b) / delta + 6) % 6);
      } else if (g === cmax) {
        h = 60 * ((b - r) / delta + 2);
      } else {
        h = 60 * ((r - g) / delta + 4);
      }
      s = delta / (255 - Math.abs(2 * l - 255));
    }
    return [h, s, l];
  }

  function hslToRgb(h, s, l) {
    var c = (255 - Math.abs(2 * l - 255)) * s;
    var x = c * (1 - Math.abs(((h / 60) % 2) - 1));
    var m = l - c / 2;
    var cm = c + m;
    var xm = x + m;

    if (h < 60) {
      return [cm, xm, m];
    } else if (h < 120) {
      return [xm, cm, m];
    } else if (h < 180) {
      return [m, cm, xm];
    } else if (h < 240) {
      return [m, xm, cm];
    } else if (h < 300) {
      return [xm, m, cm];
    } else {
      return [cm, m, xm];
    }
  }

  if (offset && this.width > 0 && this.height > 0) {
    offset = ((offset % 360) + 360) % 360;
    var context = this._context;
    var imageData = context.getImageData(0, 0, this.width, this.height);
    var pixels = imageData.data;
    for (var i = 0; i < pixels.length; i += 4) {
      var hsl = rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]);
      var h = (hsl[0] + offset) % 360;
      var s = hsl[1];
      var l = hsl[2];
      var rgb = hslToRgb(h, s, l);
      pixels[i + 0] = rgb[0];
      pixels[i + 1] = rgb[1];
      pixels[i + 2] = rgb[2];
    }
    context.putImageData(imageData, 0, 0);
    this._setDirty();
  }
};

/**
 * Applies a blur effect to the bitmap.
 *
 * @method blur
 */
Bitmap.prototype.blur = function () {
  for (var i = 0; i < 2; i++) {
    var w = this.width;
    var h = this.height;
    var canvas = this._canvas;
    var context = this._context;
    var tempCanvas = document.createElement("canvas");
    var tempContext = tempCanvas.getContext("2d");
    tempCanvas.width = w + 2;
    tempCanvas.height = h + 2;
    tempContext.drawImage(canvas, 0, 0, w, h, 1, 1, w, h);
    tempContext.drawImage(canvas, 0, 0, w, 1, 1, 0, w, 1);
    tempContext.drawImage(canvas, 0, 0, 1, h, 0, 1, 1, h);
    tempContext.drawImage(canvas, 0, h - 1, w, 1, 1, h + 1, w, 1);
    tempContext.drawImage(canvas, w - 1, 0, 1, h, w + 1, 1, 1, h);
    context.save();
    context.fillStyle = "black";
    context.fillRect(0, 0, w, h);
    context.globalCompositeOperation = "lighter";
    context.globalAlpha = 1 / 9;
    for (var y = 0; y < 3; y++) {
      for (var x = 0; x < 3; x++) {
        context.drawImage(tempCanvas, x, y, w, h, 0, 0, w, h);
      }
    }
    context.restore();
  }
  this._setDirty();
};

/**
 * Add a callback function that will be called when the bitmap is loaded.
 *
 * @method addLoadListener
 * @param {Function} listner The callback function
 */
Bitmap.prototype.addLoadListener = function (listner) {
  if (!this.isReady()) {
    this._loadListeners.push(listner);
  } else {
    listner(this);
  }
};

/**
 * @method _makeFontNameText
 * @private
 */
Bitmap.prototype._makeFontNameText = function () {
  return (
    (this.fontItalic ? "Italic " : "") + this.fontSize + "px " + this.fontFace
  );
};

/**
 * @method _drawTextOutline
 * @param {String} text
 * @param {Number} tx
 * @param {Number} ty
 * @param {Number} maxWidth
 * @private
 */
Bitmap.prototype._drawTextOutline = function (text, tx, ty, maxWidth) {
  var context = this._context;
  context.strokeStyle = this.outlineColor;
  context.lineWidth = this.outlineWidth;
  context.lineJoin = "round";
  context.strokeText(text, tx, ty, maxWidth);
};

/**
 * @method _drawTextBody
 * @param {String} text
 * @param {Number} tx
 * @param {Number} ty
 * @param {Number} maxWidth
 * @private
 */
Bitmap.prototype._drawTextBody = function (text, tx, ty, maxWidth) {
  var context = this._context;
  context.fillStyle = this.textColor;
  context.fillText(text, tx, ty, maxWidth);
};

/**
 * @method _onLoad
 * @private
 */
Bitmap.prototype._onLoad = function () {
  this._image.removeEventListener("load", this._loadListener);
  this._image.removeEventListener("error", this._errorListener);

  this._renewCanvas();

  switch (this._loadingState) {
    case "requesting":
      this._loadingState = "requestCompleted";
      if (this._decodeAfterRequest) {
        this.decode();
      } else {
        this._loadingState = "purged";
        this._clearImgInstance();
      }
      break;

    case "decrypting":
      window.URL.revokeObjectURL(this._image.src);
      this._loadingState = "decryptCompleted";
      if (this._decodeAfterRequest) {
        this.decode();
      } else {
        this._loadingState = "purged";
        this._clearImgInstance();
      }
      break;
  }
};

Bitmap.prototype.decode = function () {
  switch (this._loadingState) {
    case "requestCompleted":
    case "decryptCompleted":
      this._loadingState = "loaded";

      if (!this.__canvas) this._createBaseTexture(this._image);
      this._setDirty();
      this._callLoadListeners();
      break;

    case "requesting":
    case "decrypting":
      this._decodeAfterRequest = true;
      if (!this._loader) {
        this._loader = ResourceHandler.createLoader(
          this._url,
          this._requestImage.bind(this, this._url),
          this._onError.bind(this)
        );
        this._image.removeEventListener("error", this._errorListener);
        this._image.addEventListener(
          "error",
          (this._errorListener = this._loader)
        );
      }
      break;

    case "pending":
    case "purged":
    case "error":
      this._decodeAfterRequest = true;
      this._requestImage(this._url);
      break;
  }
};

/**
 * @method _callLoadListeners
 * @private
 */
Bitmap.prototype._callLoadListeners = function () {
  while (this._loadListeners.length > 0) {
    var listener = this._loadListeners.shift();
    listener(this);
  }
};

/**
 * @method _onError
 * @private
 */
Bitmap.prototype._onError = function () {
  this._image.removeEventListener("load", this._loadListener);
  this._image.removeEventListener("error", this._errorListener);
  this._loadingState = "error";
};

/**
 * @method _setDirty
 * @private
 */
Bitmap.prototype._setDirty = function () {
  this._dirty = true;
};

/**
 * updates texture is bitmap was dirty
 * @method checkDirty
 */
Bitmap.prototype.checkDirty = function () {
  if (this._dirty) {
    this._baseTexture.update();
    this._dirty = false;
  }
};

Bitmap.request = function (url) {
  var bitmap = Object.create(Bitmap.prototype);
  bitmap._defer = true;
  bitmap.initialize();

  bitmap._url = url;
  bitmap._loadingState = "pending";

  return bitmap;
};

Bitmap.prototype._requestImage = function (url) {
  if (Bitmap._reuseImages.length !== 0) {
    this._image = Bitmap._reuseImages.pop();
  } else {
    this._image = new Image();
  }

  if (this._decodeAfterRequest && !this._loader) {
    this._loader = ResourceHandler.createLoader(
      url,
      this._requestImage.bind(this, url),
      this._onError.bind(this)
    );
  }

  this._image = new Image();
  this._url = url;
  this._loadingState = "requesting";

  if (!Decrypter.checkImgIgnore(url) && Decrypter.hasEncryptedImages) {
    this._loadingState = "decrypting";
    Decrypter.decryptImg(url, this);
  } else {
    this._image.src = url;

    this._image.addEventListener(
      "load",
      (this._loadListener = Bitmap.prototype._onLoad.bind(this))
    );
    this._image.addEventListener(
      "error",
      (this._errorListener =
        this._loader || Bitmap.prototype._onError.bind(this))
    );
  }
};

Bitmap.prototype.isRequestOnly = function () {
  return !(this._decodeAfterRequest || this.isReady());
};

Bitmap.prototype.isRequestReady = function () {
  return (
    this._loadingState !== "pending" &&
    this._loadingState !== "requesting" &&
    this._loadingState !== "decrypting"
  );
};

Bitmap.prototype.startRequest = function () {
  if (this._loadingState === "pending") {
    this._decodeAfterRequest = false;
    this._requestImage(this._url);
  }
};

//-----------------------------------------------------------------------------
/**
 * The static class that carries out graphics processing.
 *
 * @class Graphics
 */
function Graphics() {
  throw new Error("This is a static class");
}

Graphics._cssFontLoading = document.fonts && document.fonts.ready;
Graphics._fontLoaded = null;
Graphics._videoVolume = 1;

/**
 * Initializes the graphics system.
 *
 * @static
 * @method initialize
 * @param {Number} width The width of the game screen
 * @param {Number} height The height of the game screen
 * @param {String} type The type of the renderer.
 *                 'canvas', 'webgl', or 'auto'.
 */
Graphics.initialize = function (width, height, type) {
  this._width = width || 800;
  this._height = height || 600;
  this._rendererType = type || "auto";
  this._boxWidth = this._width;
  this._boxHeight = this._height;

  this._scale = 1;
  this._realScale = 1;

  this._errorShowed = false;
  this._errorPrinter = null;
  this._canvas = null;
  this._video = null;
  this._videoUnlocked = false;
  this._videoLoading = false;
  this._upperCanvas = null;
  this._renderer = null;
  this._fpsMeter = null;
  this._modeBox = null;
  this._skipCount = 0;
  this._maxSkip = 3;
  this._rendered = false;
  this._loadingImage = null;
  this._loadingCount = 0;
  this._fpsMeterToggled = false;
  this._stretchEnabled = this._defaultStretchMode();

  this._canUseDifferenceBlend = false;
  this._canUseSaturationBlend = false;
  this._hiddenCanvas = null;

  this._testCanvasBlendModes();
  this._modifyExistingElements();
  this._updateRealScale();
  this._createAllElements();
  this._disableTextSelection();
  this._disableContextMenu();
  this._setupEventHandlers();
  this._setupCssFontLoading();
};

Graphics._setupCssFontLoading = function () {
  if (Graphics._cssFontLoading) {
    document.fonts.ready
      .then(function (fonts) {
        Graphics._fontLoaded = fonts;
      })
      .catch(function (error) {
        SceneManager.onError(error);
      });
  }
};

Graphics.canUseCssFontLoading = function () {
  return !!this._cssFontLoading;
};

/**
 * The total frame count of the game screen.
 *
 * @static
 * @property frameCount
 * @type Number
 */
Graphics.frameCount = 0;

/**
 * The alias of PIXI.blendModes.NORMAL.
 *
 * @static
 * @property BLEND_NORMAL
 * @type Number
 * @final
 */
Graphics.BLEND_NORMAL = 0;

/**
 * The alias of PIXI.blendModes.ADD.
 *
 * @static
 * @property BLEND_ADD
 * @type Number
 * @final
 */
Graphics.BLEND_ADD = 1;

/**
 * The alias of PIXI.blendModes.MULTIPLY.
 *
 * @static
 * @property BLEND_MULTIPLY
 * @type Number
 * @final
 */
Graphics.BLEND_MULTIPLY = 2;

/**
 * The alias of PIXI.blendModes.SCREEN.
 *
 * @static
 * @property BLEND_SCREEN
 * @type Number
 * @final
 */
Graphics.BLEND_SCREEN = 3;

/**
 * Marks the beginning of each frame for FPSMeter.
 *
 * @static
 * @method tickStart
 */
Graphics.tickStart = function () {
  if (this._fpsMeter) {
    this._fpsMeter.tickStart();
  }
};

/**
 * Marks the end of each frame for FPSMeter.
 *
 * @static
 * @method tickEnd
 */
Graphics.tickEnd = function () {
  if (this._fpsMeter && this._rendered) {
    this._fpsMeter.tick();
  }
};

/**
 * Renders the stage to the game screen.
 *
 * @static
 * @method render
 * @param {Stage} stage The stage object to be rendered
 */
Graphics.render = function (stage) {
  if (this._skipCount === 0) {
    var startTime = Date.now();
    if (stage) {
      this._renderer.render(stage);
      if (this._renderer.gl && this._renderer.gl.flush) {
        this._renderer.gl.flush();
      }
    }
    var endTime = Date.now();
    var elapsed = endTime - startTime;
    this._skipCount = Math.min(Math.floor(elapsed / 15), this._maxSkip);
    this._rendered = true;
  } else {
    this._skipCount--;
    this._rendered = false;
  }
  this.frameCount++;
};

/**
 * Checks whether the renderer type is WebGL.
 *
 * @static
 * @method isWebGL
 * @return {Boolean} True if the renderer type is WebGL
 */
Graphics.isWebGL = function () {
  return this._renderer && this._renderer.type === PIXI.RENDERER_TYPE.WEBGL;
};

/**
 * Checks whether the current browser supports WebGL.
 *
 * @static
 * @method hasWebGL
 * @return {Boolean} True if the current browser supports WebGL.
 */
Graphics.hasWebGL = function () {
  try {
    var canvas = document.createElement("canvas");
    return !!(
      canvas.getContext("webgl") || canvas.getContext("experimental-webgl")
    );
  } catch (e) {
    return false;
  }
};

/**
 * Checks whether the canvas blend mode 'difference' is supported.
 *
 * @static
 * @method canUseDifferenceBlend
 * @return {Boolean} True if the canvas blend mode 'difference' is supported
 */
Graphics.canUseDifferenceBlend = function () {
  return this._canUseDifferenceBlend;
};

/**
 * Checks whether the canvas blend mode 'saturation' is supported.
 *
 * @static
 * @method canUseSaturationBlend
 * @return {Boolean} True if the canvas blend mode 'saturation' is supported
 */
Graphics.canUseSaturationBlend = function () {
  return this._canUseSaturationBlend;
};

/**
 * Sets the source of the "Now Loading" image.
 *
 * @static
 * @method setLoadingImage
 */
Graphics.setLoadingImage = function (src) {
  this._loadingImage = new Image();
  this._loadingImage.src = src;
};

/**
 * Initializes the counter for displaying the "Now Loading" image.
 *
 * @static
 * @method startLoading
 */
Graphics.startLoading = function () {
  this._loadingCount = 0;
};

/**
 * Increments the loading counter and displays the "Now Loading" image if necessary.
 *
 * @static
 * @method updateLoading
 */
Graphics.updateLoading = function () {
  this._loadingCount++;
  this._paintUpperCanvas();
  this._upperCanvas.style.opacity = 1;
};

/**
 * Erases the "Now Loading" image.
 *
 * @static
 * @method endLoading
 */
Graphics.endLoading = function () {
  this._clearUpperCanvas();
  this._upperCanvas.style.opacity = 0;
};

/**
 * Displays the loading error text to the screen.
 *
 * @static
 * @method printLoadingError
 * @param {String} url The url of the resource failed to load
 */
Graphics.printLoadingError = function (url) {
  if (this._errorPrinter && !this._errorShowed) {
    this._errorPrinter.innerHTML = this._makeErrorHtml(
      "Loading Error",
      "Failed to load: " + url
    );
    var button = document.createElement("button");
    button.innerHTML = "Retry";
    button.style.fontSize = "24px";
    button.style.color = "#ffffff";
    button.style.backgroundColor = "#000000";
    button.onmousedown = button.ontouchstart = function (event) {
      ResourceHandler.retry();
      event.stopPropagation();
    };
    this._errorPrinter.appendChild(button);
    this._loadingCount = -Infinity;
  }
};

/**
 * Erases the loading error text.
 *
 * @static
 * @method eraseLoadingError
 */
Graphics.eraseLoadingError = function () {
  if (this._errorPrinter && !this._errorShowed) {
    this._errorPrinter.innerHTML = "";
    this.startLoading();
  }
};

/**
 * Displays the error text to the screen.
 *
 * @static
 * @method printError
 * @param {String} name The name of the error
 * @param {String} message The message of the error
 */
Graphics.printError = function (name, message) {
  this._errorShowed = true;
  if (this._errorPrinter) {
    this._errorPrinter.innerHTML = this._makeErrorHtml(name, message);
  }
  this._applyCanvasFilter();
  this._clearUpperCanvas();
};

/**
 * Shows the FPSMeter element.
 *
 * @static
 * @method showFps
 */
Graphics.showFps = function () {
  if (this._fpsMeter) {
    this._fpsMeter.show();
    this._modeBox.style.opacity = 1;
  }
};

/**
 * Hides the FPSMeter element.
 *
 * @static
 * @method hideFps
 */
Graphics.hideFps = function () {
  if (this._fpsMeter) {
    this._fpsMeter.hide();
    this._modeBox.style.opacity = 0;
  }
};

/**
 * Loads a font file.
 *
 * @static
 * @method loadFont
 * @param {String} name The face name of the font
 * @param {String} url The url of the font file
 */
Graphics.loadFont = function (name, url) {
  var style = document.createElement("style");
  var head = document.getElementsByTagName("head");
  var rule =
    '@font-face { font-family: "' + name + '"; src: url("' + url + '"); }';
  style.type = "text/css";
  head.item(0).appendChild(style);
  style.sheet.insertRule(rule, 0);
  this._createFontLoader(name);
};

/**
 * Checks whether the font file is loaded.
 *
 * @static
 * @method isFontLoaded
 * @param {String} name The face name of the font
 * @return {Boolean} True if the font file is loaded
 */
Graphics.isFontLoaded = function (name) {
  if (Graphics._cssFontLoading) {
    if (Graphics._fontLoaded) {
      return Graphics._fontLoaded.check('10px "' + name + '"');
    }

    return false;
  } else {
    if (!this._hiddenCanvas) {
      this._hiddenCanvas = document.createElement("canvas");
    }
    var context = this._hiddenCanvas.getContext("2d");
    var text = "abcdefghijklmnopqrstuvwxyz";
    var width1, width2;
    context.font = "40px " + name + ", sans-serif";
    width1 = context.measureText(text).width;
    context.font = "40px sans-serif";
    width2 = context.measureText(text).width;
    return width1 !== width2;
  }
};

/**
 * Starts playback of a video.
 *
 * @static
 * @method playVideo
 * @param {String} src
 */
Graphics.playVideo = function (src) {
  this._videoLoader = ResourceHandler.createLoader(
    null,
    this._playVideo.bind(this, src),
    this._onVideoError.bind(this)
  );
  this._playVideo(src);
};

/**
 * @static
 * @method _playVideo
 * @param {String} src
 * @private
 */
Graphics._playVideo = function (src) {
  this._video.src = src;
  this._video.onloadeddata = this._onVideoLoad.bind(this);
  this._video.onerror = this._videoLoader;
  this._video.onended = this._onVideoEnd.bind(this);
  this._video.load();
  this._videoLoading = true;
};

/**
 * Checks whether the video is playing.
 *
 * @static
 * @method isVideoPlaying
 * @return {Boolean} True if the video is playing
 */
Graphics.isVideoPlaying = function () {
  return this._videoLoading || this._isVideoVisible();
};

/**
 * Checks whether the browser can play the specified video type.
 *
 * @static
 * @method canPlayVideoType
 * @param {String} type The video type to test support for
 * @return {Boolean} True if the browser can play the specified video type
 */
Graphics.canPlayVideoType = function (type) {
  return this._video && this._video.canPlayType(type);
};

/**
 * Sets volume of a video.
 *
 * @static
 * @method setVideoVolume
 * @param {Number} value
 */
Graphics.setVideoVolume = function (value) {
  this._videoVolume = value;
  if (this._video) {
    this._video.volume = this._videoVolume;
  }
};

/**
 * Converts an x coordinate on the page to the corresponding
 * x coordinate on the canvas area.
 *
 * @static
 * @method pageToCanvasX
 * @param {Number} x The x coordinate on the page to be converted
 * @return {Number} The x coordinate on the canvas area
 */
Graphics.pageToCanvasX = function (x) {
  if (this._canvas) {
    var left = this._canvas.offsetLeft;
    return Math.round((x - left) / this._realScale);
  } else {
    return 0;
  }
};

/**
 * Converts a y coordinate on the page to the corresponding
 * y coordinate on the canvas area.
 *
 * @static
 * @method pageToCanvasY
 * @param {Number} y The y coordinate on the page to be converted
 * @return {Number} The y coordinate on the canvas area
 */
Graphics.pageToCanvasY = function (y) {
  if (this._canvas) {
    var top = this._canvas.offsetTop;
    return Math.round((y - top) / this._realScale);
  } else {
    return 0;
  }
};

/**
 * Checks whether the specified point is inside the game canvas area.
 *
 * @static
 * @method isInsideCanvas
 * @param {Number} x The x coordinate on the canvas area
 * @param {Number} y The y coordinate on the canvas area
 * @return {Boolean} True if the specified point is inside the game canvas area
 */
Graphics.isInsideCanvas = function (x, y) {
  return x >= 0 && x < this._width && y >= 0 && y < this._height;
};

/**
 * Calls pixi.js garbage collector
 */
Graphics.callGC = function () {
  if (Graphics.isWebGL()) {
    Graphics._renderer.textureGC.run();
  }
};

/**
 * The width of the game screen.
 *
 * @static
 * @property width
 * @type Number
 */
Object.defineProperty(Graphics, "width", {
  get: function () {
    return this._width;
  },
  set: function (value) {
    if (this._width !== value) {
      this._width = value;
      this._updateAllElements();
    }
  },
  configurable: true,
});

/**
 * The height of the game screen.
 *
 * @static
 * @property height
 * @type Number
 */
Object.defineProperty(Graphics, "height", {
  get: function () {
    return this._height;
  },
  set: function (value) {
    if (this._height !== value) {
      this._height = value;
      this._updateAllElements();
    }
  },
  configurable: true,
});

/**
 * The width of the window display area.
 *
 * @static
 * @property boxWidth
 * @type Number
 */
Object.defineProperty(Graphics, "boxWidth", {
  get: function () {
    return this._boxWidth;
  },
  set: function (value) {
    this._boxWidth = value;
  },
  configurable: true,
});

/**
 * The height of the window display area.
 *
 * @static
 * @property boxHeight
 * @type Number
 */
Object.defineProperty(Graphics, "boxHeight", {
  get: function () {
    return this._boxHeight;
  },
  set: function (value) {
    this._boxHeight = value;
  },
  configurable: true,
});

/**
 * The zoom scale of the game screen.
 *
 * @static
 * @property scale
 * @type Number
 */
Object.defineProperty(Graphics, "scale", {
  get: function () {
    return this._scale;
  },
  set: function (value) {
    if (this._scale !== value) {
      this._scale = value;
      this._updateAllElements();
    }
  },
  configurable: true,
});

/**
 * @static
 * @method _createAllElements
 * @private
 */
Graphics._createAllElements = function () {
  this._createErrorPrinter();
  this._createCanvas();
  this._createVideo();
  this._createUpperCanvas();
  this._createRenderer();
  this._createFPSMeter();
  this._createModeBox();
  this._createGameFontLoader();
};

/**
 * @static
 * @method _updateAllElements
 * @private
 */
Graphics._updateAllElements = function () {
  this._updateRealScale();
  this._updateErrorPrinter();
  this._updateCanvas();
  this._updateVideo();
  this._updateUpperCanvas();
  this._updateRenderer();
  this._paintUpperCanvas();
};

/**
 * @static
 * @method _updateRealScale
 * @private
 */
Graphics._updateRealScale = function () {
  if (this._stretchEnabled) {
    var h = window.innerWidth / this._width;
    var v = window.innerHeight / this._height;
    if (h >= 1 && h - 0.01 <= 1) h = 1;
    if (v >= 1 && v - 0.01 <= 1) v = 1;
    this._realScale = Math.min(h, v);
  } else {
    this._realScale = this._scale;
  }
};

/**
 * @static
 * @method _makeErrorHtml
 * @param {String} name
 * @param {String} message
 * @return {String}
 * @private
 */
Graphics._makeErrorHtml = function (name, message) {
  return (
    '<font color="yellow"><b>' +
    name +
    "</b></font><br>" +
    '<font color="white">' +
    message +
    "</font><br>"
  );
};

/**
 * @static
 * @method _defaultStretchMode
 * @private
 */
Graphics._defaultStretchMode = function () {
  return Utils.isNwjs() || Utils.isMobileDevice();
};

/**
 * @static
 * @method _testCanvasBlendModes
 * @private
 */
Graphics._testCanvasBlendModes = function () {
  var canvas, context, imageData1, imageData2;
  canvas = document.createElement("canvas");
  canvas.width = 1;
  canvas.height = 1;
  context = canvas.getContext("2d");
  context.globalCompositeOperation = "source-over";
  context.fillStyle = "white";
  context.fillRect(0, 0, 1, 1);
  context.globalCompositeOperation = "difference";
  context.fillStyle = "white";
  context.fillRect(0, 0, 1, 1);
  imageData1 = context.getImageData(0, 0, 1, 1);
  context.globalCompositeOperation = "source-over";
  context.fillStyle = "black";
  context.fillRect(0, 0, 1, 1);
  context.globalCompositeOperation = "saturation";
  context.fillStyle = "white";
  context.fillRect(0, 0, 1, 1);
  imageData2 = context.getImageData(0, 0, 1, 1);
  this._canUseDifferenceBlend = imageData1.data[0] === 0;
  this._canUseSaturationBlend = imageData2.data[0] === 0;
};

/**
 * @static
 * @method _modifyExistingElements
 * @private
 */
Graphics._modifyExistingElements = function () {
  var elements = document.getElementsByTagName("*");
  for (var i = 0; i < elements.length; i++) {
    if (elements[i].style.zIndex > 0) {
      elements[i].style.zIndex = 0;
    }
  }
};

/**
 * @static
 * @method _createErrorPrinter
 * @private
 */
Graphics._createErrorPrinter = function () {
  this._errorPrinter = document.createElement("p");
  this._errorPrinter.id = "ErrorPrinter";
  this._updateErrorPrinter();
  document.body.appendChild(this._errorPrinter);
};

/**
 * @static
 * @method _updateErrorPrinter
 * @private
 */
Graphics._updateErrorPrinter = function () {
  this._errorPrinter.width = this._width * 0.9;
  this._errorPrinter.height = 40;
  this._errorPrinter.style.textAlign = "center";
  this._errorPrinter.style.textShadow = "1px 1px 3px #000";
  this._errorPrinter.style.fontSize = "20px";
  this._errorPrinter.style.zIndex = 99;
  this._centerElement(this._errorPrinter);
};

/**
 * @static
 * @method _createCanvas
 * @private
 */
Graphics._createCanvas = function () {
  this._canvas = document.createElement("canvas");
  this._canvas.id = "GameCanvas";
  this._updateCanvas();
  document.body.appendChild(this._canvas);
};

/**
 * @static
 * @method _updateCanvas
 * @private
 */
Graphics._updateCanvas = function () {
  this._canvas.width = this._width;
  this._canvas.height = this._height;
  this._canvas.style.zIndex = 1;
  this._centerElement(this._canvas);
};

/**
 * @static
 * @method _createVideo
 * @private
 */
Graphics._createVideo = function () {
  this._video = document.createElement("video");
  this._video.id = "GameVideo";
  this._video.style.opacity = 0;
  this._video.setAttribute("playsinline", "");
  this._video.volume = this._videoVolume;
  this._updateVideo();
  makeVideoPlayableInline(this._video);
  document.body.appendChild(this._video);
};

/**
 * @static
 * @method _updateVideo
 * @private
 */
Graphics._updateVideo = function () {
  this._video.width = this._width;
  this._video.height = this._height;
  this._video.style.zIndex = 2;
  this._centerElement(this._video);
};

/**
 * @static
 * @method _createUpperCanvas
 * @private
 */
Graphics._createUpperCanvas = function () {
  this._upperCanvas = document.createElement("canvas");
  this._upperCanvas.id = "UpperCanvas";
  this._updateUpperCanvas();
  document.body.appendChild(this._upperCanvas);
};

/**
 * @static
 * @method _updateUpperCanvas
 * @private
 */
Graphics._updateUpperCanvas = function () {
  this._upperCanvas.width = this._width;
  this._upperCanvas.height = this._height;
  this._upperCanvas.style.zIndex = 3;
  this._centerElement(this._upperCanvas);
};

/**
 * @static
 * @method _clearUpperCanvas
 * @private
 */
Graphics._clearUpperCanvas = function () {
  var context = this._upperCanvas.getContext("2d");
  context.clearRect(0, 0, this._width, this._height);
};

/**
 * @static
 * @method _paintUpperCanvas
 * @private
 */
Graphics._paintUpperCanvas = function () {
  this._clearUpperCanvas();
  if (this._loadingImage && this._loadingCount >= 20) {
    var context = this._upperCanvas.getContext("2d");
    var dx = (this._width - this._loadingImage.width) / 2;
    var dy = (this._height - this._loadingImage.height) / 2;
    var alpha = ((this._loadingCount - 20) / 30).clamp(0, 1);
    context.save();
    context.globalAlpha = alpha;
    context.drawImage(this._loadingImage, dx, dy);
    context.restore();
  }
};

/**
 * @static
 * @method _createRenderer
 * @private
 */
Graphics._createRenderer = function () {
  PIXI.dontSayHello = true;
  var width = this._width;
  var height = this._height;
  var options = { view: this._canvas };
  try {
    switch (this._rendererType) {
      case "canvas":
        this._renderer = new PIXI.CanvasRenderer(width, height, options);
        break;
      case "webgl":
        this._renderer = new PIXI.WebGLRenderer(width, height, options);
        break;
      default:
        this._renderer = PIXI.autoDetectRenderer(width, height, options);
        break;
    }

    if (this._renderer && this._renderer.textureGC)
      this._renderer.textureGC.maxIdle = 1;
  } catch (e) {
    this._renderer = null;
  }
};

/**
 * @static
 * @method _updateRenderer
 * @private
 */
Graphics._updateRenderer = function () {
  if (this._renderer) {
    this._renderer.resize(this._width, this._height);
  }
};

/**
 * @static
 * @method _createFPSMeter
 * @private
 */
Graphics._createFPSMeter = function () {
  var options = { graph: 1, decimals: 0, theme: "transparent", toggleOn: null };
  this._fpsMeter = new FPSMeter(options);
  this._fpsMeter.hide();
};

/**
 * @static
 * @method _createModeBox
 * @private
 */
Graphics._createModeBox = function () {
  var box = document.createElement("div");
  box.id = "modeTextBack";
  box.style.position = "absolute";
  box.style.left = "5px";
  box.style.top = "5px";
  box.style.width = "119px";
  box.style.height = "58px";
  box.style.background = "rgba(0,0,0,0.2)";
  box.style.zIndex = 9;
  box.style.opacity = 0;

  var text = document.createElement("div");
  text.id = "modeText";
  text.style.position = "absolute";
  text.style.left = "0px";
  text.style.top = "41px";
  text.style.width = "119px";
  text.style.fontSize = "12px";
  text.style.fontFamily = "monospace";
  text.style.color = "white";
  text.style.textAlign = "center";
  text.style.textShadow = "1px 1px 0 rgba(0,0,0,0.5)";
  text.innerHTML = this.isWebGL() ? "WebGL mode" : "Canvas mode";

  document.body.appendChild(box);
  box.appendChild(text);

  this._modeBox = box;
};

/**
 * @static
 * @method _createGameFontLoader
 * @private
 */
Graphics._createGameFontLoader = function () {
  this._createFontLoader("GameFont");
};

/**
 * @static
 * @method _createFontLoader
 * @param {String} name
 * @private
 */
Graphics._createFontLoader = function (name) {
  var div = document.createElement("div");
  var text = document.createTextNode(".");
  div.style.fontFamily = name;
  div.style.fontSize = "0px";
  div.style.color = "transparent";
  div.style.position = "absolute";
  div.style.margin = "auto";
  div.style.top = "0px";
  div.style.left = "0px";
  div.style.width = "1px";
  div.style.height = "1px";
  div.appendChild(text);
  document.body.appendChild(div);
};

/**
 * @static
 * @method _centerElement
 * @param {HTMLElement} element
 * @private
 */
Graphics._centerElement = function (element) {
  var width = element.width * this._realScale;
  var height = element.height * this._realScale;
  element.style.position = "absolute";
  element.style.margin = "auto";
  element.style.top = 0;
  element.style.left = 0;
  element.style.right = 0;
  element.style.bottom = 0;
  element.style.width = width + "px";
  element.style.height = height + "px";
};

/**
 * @static
 * @method _disableTextSelection
 * @private
 */
Graphics._disableTextSelection = function () {
  var body = document.body;
  body.style.userSelect = "none";
  body.style.webkitUserSelect = "none";
  body.style.msUserSelect = "none";
  body.style.mozUserSelect = "none";
};

/**
 * @static
 * @method _disableContextMenu
 * @private
 */
Graphics._disableContextMenu = function () {
  var elements = document.body.getElementsByTagName("*");
  var oncontextmenu = function () {
    return false;
  };
  for (var i = 0; i < elements.length; i++) {
    elements[i].oncontextmenu = oncontextmenu;
  }
};

/**
 * @static
 * @method _applyCanvasFilter
 * @private
 */
Graphics._applyCanvasFilter = function () {
  if (this._canvas) {
    this._canvas.style.opacity = 0.5;
    this._canvas.style.filter = "blur(8px)";
    this._canvas.style.webkitFilter = "blur(8px)";
  }
};

/**
 * @static
 * @method _onVideoLoad
 * @private
 */
Graphics._onVideoLoad = function () {
  this._video.play();
  this._updateVisibility(true);
  this._videoLoading = false;
};

/**
 * @static
 * @method _onVideoError
 * @private
 */
Graphics._onVideoError = function () {
  this._updateVisibility(false);
  this._videoLoading = false;
};

/**
 * @static
 * @method _onVideoEnd
 * @private
 */
Graphics._onVideoEnd = function () {
  this._updateVisibility(false);
};

/**
 * @static
 * @method _updateVisibility
 * @param {Boolean} videoVisible
 * @private
 */
Graphics._updateVisibility = function (videoVisible) {
  this._video.style.opacity = videoVisible ? 1 : 0;
  this._canvas.style.opacity = videoVisible ? 0 : 1;
};

/**
 * @static
 * @method _isVideoVisible
 * @return {Boolean}
 * @private
 */
Graphics._isVideoVisible = function () {
  return this._video.style.opacity > 0;
};

/**
 * @static
 * @method _setupEventHandlers
 * @private
 */
Graphics._setupEventHandlers = function () {
  window.addEventListener("resize", this._onWindowResize.bind(this));
  document.addEventListener("keydown", this._onKeyDown.bind(this));
  document.addEventListener("keydown", this._onTouchEnd.bind(this));
  document.addEventListener("mousedown", this._onTouchEnd.bind(this));
  document.addEventListener("touchend", this._onTouchEnd.bind(this));
};

/**
 * @static
 * @method _onWindowResize
 * @private
 */
Graphics._onWindowResize = function () {
  this._updateAllElements();
};

/**
 * @static
 * @method _onKeyDown
 * @param {KeyboardEvent} event
 * @private
 */
Graphics._onKeyDown = function (event) {
  if (!event.ctrlKey && !event.altKey) {
    switch (event.keyCode) {
      case 113: // F2
        event.preventDefault();
        this._switchFPSMeter();
        break;
      case 114: // F3
        event.preventDefault();
        this._switchStretchMode();
        break;
      case 115: // F4
        event.preventDefault();
        this._switchFullScreen();
        break;
    }
  }
};

/**
 * @static
 * @method _onTouchEnd
 * @param {TouchEvent} event
 * @private
 */
Graphics._onTouchEnd = function (event) {
  if (!this._videoUnlocked) {
    this._video.play();
    this._videoUnlocked = true;
  }
  if (this._isVideoVisible() && this._video.paused) {
    this._video.play();
  }
};

/**
 * @static
 * @method _switchFPSMeter
 * @private
 */
Graphics._switchFPSMeter = function () {
  if (this._fpsMeter.isPaused) {
    this.showFps();
    this._fpsMeter.showFps();
    this._fpsMeterToggled = false;
  } else if (!this._fpsMeterToggled) {
    this._fpsMeter.showDuration();
    this._fpsMeterToggled = true;
  } else {
    this.hideFps();
  }
};

/**
 * @static
 * @method _switchStretchMode
 * @return {Boolean}
 * @private
 */
Graphics._switchStretchMode = function () {
  this._stretchEnabled = !this._stretchEnabled;
  this._updateAllElements();
};

/**
 * @static
 * @method _switchFullScreen
 * @private
 */
Graphics._switchFullScreen = function () {
  if (this._isFullScreen()) {
    this._requestFullScreen();
  } else {
    this._cancelFullScreen();
  }
};

/**
 * @static
 * @method _isFullScreen
 * @return {Boolean}
 * @private
 */
Graphics._isFullScreen = function () {
  return (
    (document.fullScreenElement && document.fullScreenElement !== null) ||
    (!document.mozFullScreen &&
      !document.webkitFullscreenElement &&
      !document.msFullscreenElement)
  );
};

/**
 * @static
 * @method _requestFullScreen
 * @private
 */
Graphics._requestFullScreen = function () {
  var element = document.body;
  if (element.requestFullScreen) {
    element.requestFullScreen();
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen();
  } else if (element.webkitRequestFullScreen) {
    element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen();
  }
};

/**
 * @static
 * @method _cancelFullScreen
 * @private
 */
Graphics._cancelFullScreen = function () {
  if (document.cancelFullScreen) {
    document.cancelFullScreen();
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen();
  } else if (document.webkitCancelFullScreen) {
    document.webkitCancelFullScreen();
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen();
  }
};

//-----------------------------------------------------------------------------
/**
 * The static class that handles input data from the keyboard and gamepads.
 *
 * @class Input
 */
function Input() {
  throw new Error("This is a static class");
}

/**
 * Initializes the input system.
 *
 * @static
 * @method initialize
 */
Input.initialize = function () {
  this.clear();
  this._wrapNwjsAlert();
  this._setupEventHandlers();
};

/**
 * The wait time of the key repeat in frames.
 *
 * @static
 * @property keyRepeatWait
 * @type Number
 */
Input.keyRepeatWait = 24;

/**
 * The interval of the key repeat in frames.
 *
 * @static
 * @property keyRepeatInterval
 * @type Number
 */
Input.keyRepeatInterval = 6;

/**
 * A hash table to convert from a virtual key code to a mapped key name.
 *
 * @static
 * @property keyMapper
 * @type Object
 */
Input.keyMapper = {
  9: "tab", // tab
  13: "ok", // enter
  16: "shift", // shift
  17: "control", // control
  18: "control", // alt
  27: "escape", // escape
  32: "ok", // space
  33: "pageup", // pageup
  34: "pagedown", // pagedown
  37: "left", // left arrow
  38: "up", // up arrow
  39: "right", // right arrow
  40: "down", // down arrow
  45: "escape", // insert
  81: "pageup", // Q
  87: "pagedown", // W
  88: "escape", // X
  90: "ok", // Z
  96: "escape", // numpad 0
  98: "down", // numpad 2
  100: "left", // numpad 4
  102: "right", // numpad 6
  104: "up", // numpad 8
  120: "debug", // F9
};

/**
 * A hash table to convert from a gamepad button to a mapped key name.
 *
 * @static
 * @property gamepadMapper
 * @type Object
 */
Input.gamepadMapper = {
  0: "ok", // A
  1: "cancel", // B
  2: "shift", // X
  3: "menu", // Y
  4: "pageup", // LB
  5: "pagedown", // RB
  12: "up", // D-pad up
  13: "down", // D-pad down
  14: "left", // D-pad left
  15: "right", // D-pad right
};

/**
 * Clears all the input data.
 *
 * @static
 * @method clear
 */
Input.clear = function () {
  this._currentState = {};
  this._previousState = {};
  this._gamepadStates = [];
  this._latestButton = null;
  this._pressedTime = 0;
  this._dir4 = 0;
  this._dir8 = 0;
  this._preferredAxis = "";
  this._date = 0;
};

/**
 * Updates the input data.
 *
 * @static
 * @method update
 */
Input.update = function () {
  this._pollGamepads();
  if (this._currentState[this._latestButton]) {
    this._pressedTime++;
  } else {
    this._latestButton = null;
  }
  for (var name in this._currentState) {
    if (this._currentState[name] && !this._previousState[name]) {
      this._latestButton = name;
      this._pressedTime = 0;
      this._date = Date.now();
    }
    this._previousState[name] = this._currentState[name];
  }
  this._updateDirection();
};

/**
 * Checks whether a key is currently pressed down.
 *
 * @static
 * @method isPressed
 * @param {String} keyName The mapped name of the key
 * @return {Boolean} True if the key is pressed
 */
Input.isPressed = function (keyName) {
  if (this._isEscapeCompatible(keyName) && this.isPressed("escape")) {
    return true;
  } else {
    return !!this._currentState[keyName];
  }
};

/**
 * Checks whether a key is just pressed.
 *
 * @static
 * @method isTriggered
 * @param {String} keyName The mapped name of the key
 * @return {Boolean} True if the key is triggered
 */
Input.isTriggered = function (keyName) {
  if (this._isEscapeCompatible(keyName) && this.isTriggered("escape")) {
    return true;
  } else {
    return this._latestButton === keyName && this._pressedTime === 0;
  }
};

/**
 * Checks whether a key is just pressed or a key repeat occurred.
 *
 * @static
 * @method isRepeated
 * @param {String} keyName The mapped name of the key
 * @return {Boolean} True if the key is repeated
 */
Input.isRepeated = function (keyName) {
  if (this._isEscapeCompatible(keyName) && this.isRepeated("escape")) {
    return true;
  } else {
    return (
      this._latestButton === keyName &&
      (this._pressedTime === 0 ||
        (this._pressedTime >= this.keyRepeatWait &&
          this._pressedTime % this.keyRepeatInterval === 0))
    );
  }
};

/**
 * Checks whether a key is kept depressed.
 *
 * @static
 * @method isLongPressed
 * @param {String} keyName The mapped name of the key
 * @return {Boolean} True if the key is long-pressed
 */
Input.isLongPressed = function (keyName) {
  if (this._isEscapeCompatible(keyName) && this.isLongPressed("escape")) {
    return true;
  } else {
    return (
      this._latestButton === keyName && this._pressedTime >= this.keyRepeatWait
    );
  }
};

/**
 * [read-only] The four direction value as a number of the numpad, or 0 for neutral.
 *
 * @static
 * @property dir4
 * @type Number
 */
Object.defineProperty(Input, "dir4", {
  get: function () {
    return this._dir4;
  },
  configurable: true,
});

/**
 * [read-only] The eight direction value as a number of the numpad, or 0 for neutral.
 *
 * @static
 * @property dir8
 * @type Number
 */
Object.defineProperty(Input, "dir8", {
  get: function () {
    return this._dir8;
  },
  configurable: true,
});

/**
 * [read-only] The time of the last input in milliseconds.
 *
 * @static
 * @property date
 * @type Number
 */
Object.defineProperty(Input, "date", {
  get: function () {
    return this._date;
  },
  configurable: true,
});

/**
 * @static
 * @method _wrapNwjsAlert
 * @private
 */
Input._wrapNwjsAlert = function () {
  if (Utils.isNwjs()) {
    var _alert = window.alert;
    window.alert = function () {
      var gui = require("nw.gui");
      var win = gui.Window.get();
      _alert.apply(this, arguments);
      win.focus();
      Input.clear();
    };
  }
};

/**
 * @static
 * @method _setupEventHandlers
 * @private
 */
Input._setupEventHandlers = function () {
  document.addEventListener("keydown", this._onKeyDown.bind(this));
  document.addEventListener("keyup", this._onKeyUp.bind(this));
  window.addEventListener("blur", this._onLostFocus.bind(this));
};

/**
 * @static
 * @method _onKeyDown
 * @param {KeyboardEvent} event
 * @private
 */
Input._onKeyDown = function (event) {
  if (this._shouldPreventDefault(event.keyCode)) {
    event.preventDefault();
  }
  if (event.keyCode === 144) {
    // Numlock
    this.clear();
  }
  var buttonName = this.keyMapper[event.keyCode];
  if (ResourceHandler.exists() && buttonName === "ok") {
    ResourceHandler.retry();
  } else if (buttonName) {
    this._currentState[buttonName] = true;
  }
};

/**
 * @static
 * @method _shouldPreventDefault
 * @param {Number} keyCode
 * @private
 */
Input._shouldPreventDefault = function (keyCode) {
  switch (keyCode) {
    case 8: // backspace
    case 33: // pageup
    case 34: // pagedown
    case 37: // left arrow
    case 38: // up arrow
    case 39: // right arrow
    case 40: // down arrow
      return true;
  }
  return false;
};

/**
 * @static
 * @method _onKeyUp
 * @param {KeyboardEvent} event
 * @private
 */
Input._onKeyUp = function (event) {
  var buttonName = this.keyMapper[event.keyCode];
  if (buttonName) {
    this._currentState[buttonName] = false;
  }
  if (event.keyCode === 0) {
    // For QtWebEngine on OS X
    this.clear();
  }
};

/**
 * @static
 * @method _onLostFocus
 * @private
 */
Input._onLostFocus = function () {
  this.clear();
};

/**
 * @static
 * @method _pollGamepads
 * @private
 */
Input._pollGamepads = function () {
  if (navigator.getGamepads) {
    var gamepads = navigator.getGamepads();
    if (gamepads) {
      for (var i = 0; i < gamepads.length; i++) {
        var gamepad = gamepads[i];
        if (gamepad && gamepad.connected) {
          this._updateGamepadState(gamepad);
        }
      }
    }
  }
};

/**
 * @static
 * @method _updateGamepadState
 * @param {Gamepad} gamepad
 * @param {Number} index
 * @private
 */
Input._updateGamepadState = function (gamepad) {
  var lastState = this._gamepadStates[gamepad.index] || [];
  var newState = [];
  var buttons = gamepad.buttons;
  var axes = gamepad.axes;
  var threshold = 0.5;
  newState[12] = false;
  newState[13] = false;
  newState[14] = false;
  newState[15] = false;
  for (var i = 0; i < buttons.length; i++) {
    newState[i] = buttons[i].pressed;
  }
  if (axes[1] < -threshold) {
    newState[12] = true; // up
  } else if (axes[1] > threshold) {
    newState[13] = true; // down
  }
  if (axes[0] < -threshold) {
    newState[14] = true; // left
  } else if (axes[0] > threshold) {
    newState[15] = true; // right
  }
  for (var j = 0; j < newState.length; j++) {
    if (newState[j] !== lastState[j]) {
      var buttonName = this.gamepadMapper[j];
      if (buttonName) {
        this._currentState[buttonName] = newState[j];
      }
    }
  }
  this._gamepadStates[gamepad.index] = newState;
};

/**
 * @static
 * @method _updateDirection
 * @private
 */
Input._updateDirection = function () {
  var x = this._signX();
  var y = this._signY();

  this._dir8 = this._makeNumpadDirection(x, y);

  if (x !== 0 && y !== 0) {
    if (this._preferredAxis === "x") {
      y = 0;
    } else {
      x = 0;
    }
  } else if (x !== 0) {
    this._preferredAxis = "y";
  } else if (y !== 0) {
    this._preferredAxis = "x";
  }

  this._dir4 = this._makeNumpadDirection(x, y);
};

/**
 * @static
 * @method _signX
 * @private
 */
Input._signX = function () {
  var x = 0;

  if (this.isPressed("left")) {
    x--;
  }
  if (this.isPressed("right")) {
    x++;
  }
  return x;
};

/**
 * @static
 * @method _signY
 * @private
 */
Input._signY = function () {
  var y = 0;

  if (this.isPressed("up")) {
    y--;
  }
  if (this.isPressed("down")) {
    y++;
  }
  return y;
};

/**
 * @static
 * @method _makeNumpadDirection
 * @param {Number} x
 * @param {Number} y
 * @return {Number}
 * @private
 */
Input._makeNumpadDirection = function (x, y) {
  if (x !== 0 || y !== 0) {
    return 5 - y * 3 + x;
  }
  return 0;
};

/**
 * @static
 * @method _isEscapeCompatible
 * @param {String} keyName
 * @return {Boolean}
 * @private
 */
Input._isEscapeCompatible = function (keyName) {
  return keyName === "cancel" || keyName === "menu";
};

//-----------------------------------------------------------------------------
/**
 * The static class that handles input data from the mouse and touchscreen.
 *
 * @class TouchInput
 */
function TouchInput() {
  throw new Error("This is a static class");
}

/**
 * Initializes the touch system.
 *
 * @static
 * @method initialize
 */
TouchInput.initialize = function () {
  this.clear();
  this._setupEventHandlers();
};

/**
 * The wait time of the pseudo key repeat in frames.
 *
 * @static
 * @property keyRepeatWait
 * @type Number
 */
TouchInput.keyRepeatWait = 24;

/**
 * The interval of the pseudo key repeat in frames.
 *
 * @static
 * @property keyRepeatInterval
 * @type Number
 */
TouchInput.keyRepeatInterval = 6;

/**
 * Clears all the touch data.
 *
 * @static
 * @method clear
 */
TouchInput.clear = function () {
  this._mousePressed = false;
  this._screenPressed = false;
  this._pressedTime = 0;
  this._events = {};
  this._events.triggered = false;
  this._events.cancelled = false;
  this._events.moved = false;
  this._events.released = false;
  this._events.wheelX = 0;
  this._events.wheelY = 0;
  this._triggered = false;
  this._cancelled = false;
  this._moved = false;
  this._released = false;
  this._wheelX = 0;
  this._wheelY = 0;
  this._x = 0;
  this._y = 0;
  this._date = 0;
};

/**
 * Updates the touch data.
 *
 * @static
 * @method update
 */
TouchInput.update = function () {
  this._triggered = this._events.triggered;
  this._cancelled = this._events.cancelled;
  this._moved = this._events.moved;
  this._released = this._events.released;
  this._wheelX = this._events.wheelX;
  this._wheelY = this._events.wheelY;
  this._events.triggered = false;
  this._events.cancelled = false;
  this._events.moved = false;
  this._events.released = false;
  this._events.wheelX = 0;
  this._events.wheelY = 0;
  if (this.isPressed()) {
    this._pressedTime++;
  }
};

/**
 * Checks whether the mouse button or touchscreen is currently pressed down.
 *
 * @static
 * @method isPressed
 * @return {Boolean} True if the mouse button or touchscreen is pressed
 */
TouchInput.isPressed = function () {
  return this._mousePressed || this._screenPressed;
};

/**
 * Checks whether the left mouse button or touchscreen is just pressed.
 *
 * @static
 * @method isTriggered
 * @return {Boolean} True if the mouse button or touchscreen is triggered
 */
TouchInput.isTriggered = function () {
  return this._triggered;
};

/**
 * Checks whether the left mouse button or touchscreen is just pressed
 * or a pseudo key repeat occurred.
 *
 * @static
 * @method isRepeated
 * @return {Boolean} True if the mouse button or touchscreen is repeated
 */
TouchInput.isRepeated = function () {
  return (
    this.isPressed() &&
    (this._triggered ||
      (this._pressedTime >= this.keyRepeatWait &&
        this._pressedTime % this.keyRepeatInterval === 0))
  );
};

/**
 * Checks whether the left mouse button or touchscreen is kept depressed.
 *
 * @static
 * @method isLongPressed
 * @return {Boolean} True if the left mouse button or touchscreen is long-pressed
 */
TouchInput.isLongPressed = function () {
  return this.isPressed() && this._pressedTime >= this.keyRepeatWait;
};

/**
 * Checks whether the right mouse button is just pressed.
 *
 * @static
 * @method isCancelled
 * @return {Boolean} True if the right mouse button is just pressed
 */
TouchInput.isCancelled = function () {
  return this._cancelled;
};

/**
 * Checks whether the mouse or a finger on the touchscreen is moved.
 *
 * @static
 * @method isMoved
 * @return {Boolean} True if the mouse or a finger on the touchscreen is moved
 */
TouchInput.isMoved = function () {
  return this._moved;
};

/**
 * Checks whether the left mouse button or touchscreen is released.
 *
 * @static
 * @method isReleased
 * @return {Boolean} True if the mouse button or touchscreen is released
 */
TouchInput.isReleased = function () {
  return this._released;
};

/**
 * [read-only] The horizontal scroll amount.
 *
 * @static
 * @property wheelX
 * @type Number
 */
Object.defineProperty(TouchInput, "wheelX", {
  get: function () {
    return this._wheelX;
  },
  configurable: true,
});

/**
 * [read-only] The vertical scroll amount.
 *
 * @static
 * @property wheelY
 * @type Number
 */
Object.defineProperty(TouchInput, "wheelY", {
  get: function () {
    return this._wheelY;
  },
  configurable: true,
});

/**
 * [read-only] The x coordinate on the canvas area of the latest touch event.
 *
 * @static
 * @property x
 * @type Number
 */
Object.defineProperty(TouchInput, "x", {
  get: function () {
    return this._x;
  },
  configurable: true,
});

/**
 * [read-only] The y coordinate on the canvas area of the latest touch event.
 *
 * @static
 * @property y
 * @type Number
 */
Object.defineProperty(TouchInput, "y", {
  get: function () {
    return this._y;
  },
  configurable: true,
});

/**
 * [read-only] The time of the last input in milliseconds.
 *
 * @static
 * @property date
 * @type Number
 */
Object.defineProperty(TouchInput, "date", {
  get: function () {
    return this._date;
  },
  configurable: true,
});

/**
 * @static
 * @method _setupEventHandlers
 * @private
 */
TouchInput._setupEventHandlers = function () {
  var isSupportPassive = Utils.isSupportPassiveEvent();
  document.addEventListener("mousedown", this._onMouseDown.bind(this));
  document.addEventListener("mousemove", this._onMouseMove.bind(this));
  document.addEventListener("mouseup", this._onMouseUp.bind(this));
  document.addEventListener("wheel", this._onWheel.bind(this));
  document.addEventListener(
    "touchstart",
    this._onTouchStart.bind(this),
    isSupportPassive ? { passive: false } : false
  );
  document.addEventListener(
    "touchmove",
    this._onTouchMove.bind(this),
    isSupportPassive ? { passive: false } : false
  );
  document.addEventListener("touchend", this._onTouchEnd.bind(this));
  document.addEventListener("touchcancel", this._onTouchCancel.bind(this));
  document.addEventListener("pointerdown", this._onPointerDown.bind(this));
};

/**
 * @static
 * @method _onMouseDown
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onMouseDown = function (event) {
  if (event.button === 0) {
    this._onLeftButtonDown(event);
  } else if (event.button === 1) {
    this._onMiddleButtonDown(event);
  } else if (event.button === 2) {
    this._onRightButtonDown(event);
  }
};

/**
 * @static
 * @method _onLeftButtonDown
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onLeftButtonDown = function (event) {
  var x = Graphics.pageToCanvasX(event.pageX);
  var y = Graphics.pageToCanvasY(event.pageY);
  if (Graphics.isInsideCanvas(x, y)) {
    this._mousePressed = true;
    this._pressedTime = 0;
    this._onTrigger(x, y);
  }
};

/**
 * @static
 * @method _onMiddleButtonDown
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onMiddleButtonDown = function (event) {};

/**
 * @static
 * @method _onRightButtonDown
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onRightButtonDown = function (event) {
  var x = Graphics.pageToCanvasX(event.pageX);
  var y = Graphics.pageToCanvasY(event.pageY);
  if (Graphics.isInsideCanvas(x, y)) {
    this._onCancel(x, y);
  }
};

/**
 * @static
 * @method _onMouseMove
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onMouseMove = function (event) {
  if (this._mousePressed) {
    var x = Graphics.pageToCanvasX(event.pageX);
    var y = Graphics.pageToCanvasY(event.pageY);
    this._onMove(x, y);
  }
};

/**
 * @static
 * @method _onMouseUp
 * @param {MouseEvent} event
 * @private
 */
TouchInput._onMouseUp = function (event) {
  if (event.button === 0) {
    var x = Graphics.pageToCanvasX(event.pageX);
    var y = Graphics.pageToCanvasY(event.pageY);
    this._mousePressed = false;
    this._onRelease(x, y);
  }
};

/**
 * @static
 * @method _onWheel
 * @param {WheelEvent} event
 * @private
 */
TouchInput._onWheel = function (event) {
  this._events.wheelX += event.deltaX;
  this._events.wheelY += event.deltaY;
  event.preventDefault();
};

/**
 * @static
 * @method _onTouchStart
 * @param {TouchEvent} event
 * @private
 */
TouchInput._onTouchStart = function (event) {
  for (var i = 0; i < event.changedTouches.length; i++) {
    var touch = event.changedTouches[i];
    var x = Graphics.pageToCanvasX(touch.pageX);
    var y = Graphics.pageToCanvasY(touch.pageY);
    if (Graphics.isInsideCanvas(x, y)) {
      this._screenPressed = true;
      this._pressedTime = 0;
      if (event.touches.length >= 2) {
        this._onCancel(x, y);
      } else {
        this._onTrigger(x, y);
      }
      event.preventDefault();
    }
  }
  if (window.cordova || window.navigator.standalone) {
    event.preventDefault();
  }
};

/**
 * @static
 * @method _onTouchMove
 * @param {TouchEvent} event
 * @private
 */
TouchInput._onTouchMove = function (event) {
  for (var i = 0; i < event.changedTouches.length; i++) {
    var touch = event.changedTouches[i];
    var x = Graphics.pageToCanvasX(touch.pageX);
    var y = Graphics.pageToCanvasY(touch.pageY);
    this._onMove(x, y);
  }
};

/**
 * @static
 * @method _onTouchEnd
 * @param {TouchEvent} event
 * @private
 */
TouchInput._onTouchEnd = function (event) {
  for (var i = 0; i < event.changedTouches.length; i++) {
    var touch = event.changedTouches[i];
    var x = Graphics.pageToCanvasX(touch.pageX);
    var y = Graphics.pageToCanvasY(touch.pageY);
    this._screenPressed = false;
    this._onRelease(x, y);
  }
};

/**
 * @static
 * @method _onTouchCancel
 * @param {TouchEvent} event
 * @private
 */
TouchInput._onTouchCancel = function (event) {
  this._screenPressed = false;
};

/**
 * @static
 * @method _onPointerDown
 * @param {PointerEvent} event
 * @private
 */
TouchInput._onPointerDown = function (event) {
  if (event.pointerType === "touch" && !event.isPrimary) {
    var x = Graphics.pageToCanvasX(event.pageX);
    var y = Graphics.pageToCanvasY(event.pageY);
    if (Graphics.isInsideCanvas(x, y)) {
      // For Microsoft Edge
      this._onCancel(x, y);
      event.preventDefault();
    }
  }
};

/**
 * @static
 * @method _onTrigger
 * @param {Number} x
 * @param {Number} y
 * @private
 */
TouchInput._onTrigger = function (x, y) {
  this._events.triggered = true;
  this._x = x;
  this._y = y;
  this._date = Date.now();
};

/**
 * @static
 * @method _onCancel
 * @param {Number} x
 * @param {Number} y
 * @private
 */
TouchInput._onCancel = function (x, y) {
  this._events.cancelled = true;
  this._x = x;
  this._y = y;
};

/**
 * @static
 * @method _onMove
 * @param {Number} x
 * @param {Number} y
 * @private
 */
TouchInput._onMove = function (x, y) {
  this._events.moved = true;
  this._x = x;
  this._y = y;
};

/**
 * @static
 * @method _onRelease
 * @param {Number} x
 * @param {Number} y
 * @private
 */
TouchInput._onRelease = function (x, y) {
  this._events.released = true;
  this._x = x;
  this._y = y;
};

//-----------------------------------------------------------------------------
/**
 * The basic object that is rendered to the game screen.
 *
 * @class Sprite
 * @constructor
 * @param {Bitmap} bitmap The image for the sprite
 */
function Sprite() {
  this.initialize.apply(this, arguments);
}

Sprite.prototype = Object.create(PIXI.Sprite.prototype);
Sprite.prototype.constructor = Sprite;

Sprite.voidFilter = new PIXI.filters.VoidFilter();

Sprite.prototype.initialize = function (bitmap) {
  var texture = new PIXI.Texture(new PIXI.BaseTexture());

  PIXI.Sprite.call(this, texture);

  this._bitmap = null;
  this._frame = new Rectangle();
  this._realFrame = new Rectangle();
  this._blendColor = [0, 0, 0, 0];
  this._colorTone = [0, 0, 0, 0];
  this._canvas = null;
  this._context = null;
  this._tintTexture = null;

  /**
   * use heavy renderer that will reduce border artifacts and apply advanced blendModes
   * @type {boolean}
   * @private
   */
  this._isPicture = false;

  this.spriteId = Sprite._counter++;
  this.opaque = false;

  this.bitmap = bitmap;
};

// Number of the created objects.
Sprite._counter = 0;

/**
 * The image for the sprite.
 *
 * @property bitmap
 * @type Bitmap
 */
Object.defineProperty(Sprite.prototype, "bitmap", {
  get: function () {
    return this._bitmap;
  },
  set: function (value) {
    if (this._bitmap !== value) {
      this._bitmap = value;

      if (value) {
        this._refreshFrame = true;
        value.addLoadListener(this._onBitmapLoad.bind(this));
      } else {
        this._refreshFrame = false;
        this.texture.frame = Rectangle.emptyRectangle;
      }
    }
  },
  configurable: true,
});

/**
 * The width of the sprite without the scale.
 *
 * @property width
 * @type Number
 */
Object.defineProperty(Sprite.prototype, "width", {
  get: function () {
    return this._frame.width;
  },
  set: function (value) {
    this._frame.width = value;
    this._refresh();
  },
  configurable: true,
});

/**
 * The height of the sprite without the scale.
 *
 * @property height
 * @type Number
 */
Object.defineProperty(Sprite.prototype, "height", {
  get: function () {
    return this._frame.height;
  },
  set: function (value) {
    this._frame.height = value;
    this._refresh();
  },
  configurable: true,
});

/**
 * The opacity of the sprite (0 to 255).
 *
 * @property opacity
 * @type Number
 */
Object.defineProperty(Sprite.prototype, "opacity", {
  get: function () {
    return this.alpha * 255;
  },
  set: function (value) {
    this.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

/**
 * Updates the sprite for each frame.
 *
 * @method update
 */
Sprite.prototype.update = function () {
  this.children.forEach(function (child) {
    if (child.update) {
      child.update();
    }
  });
};

/**
 * Sets the x and y at once.
 *
 * @method move
 * @param {Number} x The x coordinate of the sprite
 * @param {Number} y The y coordinate of the sprite
 */
Sprite.prototype.move = function (x, y) {
  this.x = x;
  this.y = y;
};

/**
 * Sets the rectagle of the bitmap that the sprite displays.
 *
 * @method setFrame
 * @param {Number} x The x coordinate of the frame
 * @param {Number} y The y coordinate of the frame
 * @param {Number} width The width of the frame
 * @param {Number} height The height of the frame
 */
Sprite.prototype.setFrame = function (x, y, width, height) {
  this._refreshFrame = false;
  var frame = this._frame;
  if (
    x !== frame.x ||
    y !== frame.y ||
    width !== frame.width ||
    height !== frame.height
  ) {
    frame.x = x;
    frame.y = y;
    frame.width = width;
    frame.height = height;
    this._refresh();
  }
};

/**
 * Gets the blend color for the sprite.
 *
 * @method getBlendColor
 * @return {Array} The blend color [r, g, b, a]
 */
Sprite.prototype.getBlendColor = function () {
  return this._blendColor.clone();
};

/**
 * Sets the blend color for the sprite.
 *
 * @method setBlendColor
 * @param {Array} color The blend color [r, g, b, a]
 */
Sprite.prototype.setBlendColor = function (color) {
  if (!(color instanceof Array)) {
    throw new Error("Argument must be an array");
  }
  if (!this._blendColor.equals(color)) {
    this._blendColor = color.clone();
    this._refresh();
  }
};

/**
 * Gets the color tone for the sprite.
 *
 * @method getColorTone
 * @return {Array} The color tone [r, g, b, gray]
 */
Sprite.prototype.getColorTone = function () {
  return this._colorTone.clone();
};

/**
 * Sets the color tone for the sprite.
 *
 * @method setColorTone
 * @param {Array} tone The color tone [r, g, b, gray]
 */
Sprite.prototype.setColorTone = function (tone) {
  if (!(tone instanceof Array)) {
    throw new Error("Argument must be an array");
  }
  if (!this._colorTone.equals(tone)) {
    this._colorTone = tone.clone();
    this._refresh();
  }
};

/**
 * @method _onBitmapLoad
 * @private
 */
Sprite.prototype._onBitmapLoad = function (bitmapLoaded) {
  if (bitmapLoaded === this._bitmap) {
    if (this._refreshFrame && this._bitmap) {
      this._refreshFrame = false;
      this._frame.width = this._bitmap.width;
      this._frame.height = this._bitmap.height;
    }
  }

  this._refresh();
};

/**
 * @method _refresh
 * @private
 */
Sprite.prototype._refresh = function () {
  var frameX = Math.floor(this._frame.x);
  var frameY = Math.floor(this._frame.y);
  var frameW = Math.floor(this._frame.width);
  var frameH = Math.floor(this._frame.height);
  var bitmapW = this._bitmap ? this._bitmap.width : 0;
  var bitmapH = this._bitmap ? this._bitmap.height : 0;
  var realX = frameX.clamp(0, bitmapW);
  var realY = frameY.clamp(0, bitmapH);
  var realW = (frameW - realX + frameX).clamp(0, bitmapW - realX);
  var realH = (frameH - realY + frameY).clamp(0, bitmapH - realY);

  this._realFrame.x = realX;
  this._realFrame.y = realY;
  this._realFrame.width = realW;
  this._realFrame.height = realH;
  this.pivot.x = frameX - realX;
  this.pivot.y = frameY - realY;

  if (realW > 0 && realH > 0) {
    if (this._needsTint()) {
      this._createTinter(realW, realH);
      this._executeTint(realX, realY, realW, realH);
      this._tintTexture.update();
      this.texture.baseTexture = this._tintTexture;
      this.texture.frame = new Rectangle(0, 0, realW, realH);
    } else {
      if (this._bitmap) {
        this.texture.baseTexture = this._bitmap.baseTexture;
      }
      this.texture.frame = this._realFrame;
    }
  } else if (this._bitmap) {
    this.texture.frame = Rectangle.emptyRectangle;
  } else {
    this.texture.baseTexture.width = Math.max(
      this.texture.baseTexture.width,
      this._frame.x + this._frame.width
    );
    this.texture.baseTexture.height = Math.max(
      this.texture.baseTexture.height,
      this._frame.y + this._frame.height
    );
    this.texture.frame = this._frame;
  }
  this.texture._updateID++;
};

/**
 * @method _isInBitmapRect
 * @param {Number} x
 * @param {Number} y
 * @param {Number} w
 * @param {Number} h
 * @return {Boolean}
 * @private
 */
Sprite.prototype._isInBitmapRect = function (x, y, w, h) {
  return (
    this._bitmap &&
    x + w > 0 &&
    y + h > 0 &&
    x < this._bitmap.width &&
    y < this._bitmap.height
  );
};

/**
 * @method _needsTint
 * @return {Boolean}
 * @private
 */
Sprite.prototype._needsTint = function () {
  var tone = this._colorTone;
  return tone[0] || tone[1] || tone[2] || tone[3] || this._blendColor[3] > 0;
};

/**
 * @method _createTinter
 * @param {Number} w
 * @param {Number} h
 * @private
 */
Sprite.prototype._createTinter = function (w, h) {
  if (!this._canvas) {
    this._canvas = document.createElement("canvas");
    this._context = this._canvas.getContext("2d");
  }

  this._canvas.width = w;
  this._canvas.height = h;

  if (!this._tintTexture) {
    this._tintTexture = new PIXI.BaseTexture(this._canvas);
  }

  this._tintTexture.width = w;
  this._tintTexture.height = h;
  this._tintTexture.scaleMode = this._bitmap.baseTexture.scaleMode;
};

/**
 * @method _executeTint
 * @param {Number} x
 * @param {Number} y
 * @param {Number} w
 * @param {Number} h
 * @private
 */
Sprite.prototype._executeTint = function (x, y, w, h) {
  var context = this._context;
  var tone = this._colorTone;
  var color = this._blendColor;

  context.globalCompositeOperation = "copy";
  context.drawImage(this._bitmap.canvas, x, y, w, h, 0, 0, w, h);

  if (Graphics.canUseSaturationBlend()) {
    var gray = Math.max(0, tone[3]);
    context.globalCompositeOperation = "saturation";
    context.fillStyle = "rgba(255,255,255," + gray / 255 + ")";
    context.fillRect(0, 0, w, h);
  }

  var r1 = Math.max(0, tone[0]);
  var g1 = Math.max(0, tone[1]);
  var b1 = Math.max(0, tone[2]);
  context.globalCompositeOperation = "lighter";
  context.fillStyle = Utils.rgbToCssColor(r1, g1, b1);
  context.fillRect(0, 0, w, h);

  if (Graphics.canUseDifferenceBlend()) {
    context.globalCompositeOperation = "difference";
    context.fillStyle = "white";
    context.fillRect(0, 0, w, h);

    var r2 = Math.max(0, -tone[0]);
    var g2 = Math.max(0, -tone[1]);
    var b2 = Math.max(0, -tone[2]);
    context.globalCompositeOperation = "lighter";
    context.fillStyle = Utils.rgbToCssColor(r2, g2, b2);
    context.fillRect(0, 0, w, h);

    context.globalCompositeOperation = "difference";
    context.fillStyle = "white";
    context.fillRect(0, 0, w, h);
  }

  var r3 = Math.max(0, color[0]);
  var g3 = Math.max(0, color[1]);
  var b3 = Math.max(0, color[2]);
  var a3 = Math.max(0, color[3]);
  context.globalCompositeOperation = "source-atop";
  context.fillStyle = Utils.rgbToCssColor(r3, g3, b3);
  context.globalAlpha = a3 / 255;
  context.fillRect(0, 0, w, h);

  context.globalCompositeOperation = "destination-in";
  context.globalAlpha = 1;
  context.drawImage(this._bitmap.canvas, x, y, w, h, 0, 0, w, h);
};

Sprite.prototype._renderCanvas_PIXI = PIXI.Sprite.prototype._renderCanvas;
Sprite.prototype._renderWebGL_PIXI = PIXI.Sprite.prototype._renderWebGL;

/**
 * @method _renderCanvas
 * @param {Object} renderer
 * @private
 */
Sprite.prototype._renderCanvas = function (renderer) {
  if (this.bitmap) {
    this.bitmap.touch();
  }
  if (this.bitmap && !this.bitmap.isReady()) {
    return;
  }

  if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
    this._renderCanvas_PIXI(renderer);
  }
};

/**
 * checks if we need to speed up custom blendmodes
 * @param renderer
 * @private
 */
Sprite.prototype._speedUpCustomBlendModes = function (renderer) {
  var picture = renderer.plugins.picture;
  var blend = this.blendMode;
  if (renderer.renderingToScreen && renderer._activeRenderTarget.root) {
    if (picture.drawModes[blend]) {
      var stage = renderer._lastObjectRendered;
      var f = stage._filters;
      if (!f || !f[0]) {
        setTimeout(function () {
          var f = stage._filters;
          if (!f || !f[0]) {
            stage.filters = [Sprite.voidFilter];
            stage.filterArea = new PIXI.Rectangle(
              0,
              0,
              Graphics.width,
              Graphics.height
            );
          }
        }, 0);
      }
    }
  }
};

/**
 * @method _renderWebGL
 * @param {Object} renderer
 * @private
 */
Sprite.prototype._renderWebGL = function (renderer) {
  if (this.bitmap) {
    this.bitmap.touch();
  }
  if (this.bitmap && !this.bitmap.isReady()) {
    return;
  }
  if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
    if (this._bitmap) {
      this._bitmap.checkDirty();
    }

    //copy of pixi-v4 internal code
    this.calculateVertices();

    if (this.pluginName === "sprite" && this._isPicture) {
      // use heavy renderer, which reduces artifacts and applies corrent blendMode,
      // but does not use multitexture optimization
      this._speedUpCustomBlendModes(renderer);
      renderer.setObjectRenderer(renderer.plugins.picture);
      renderer.plugins.picture.render(this);
    } else {
      // use pixi super-speed renderer
      renderer.setObjectRenderer(renderer.plugins[this.pluginName]);
      renderer.plugins[this.pluginName].render(this);
    }
  }
};

// The important members from Pixi.js

/**
 * The visibility of the sprite.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the sprite.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the sprite.
 *
 * @property y
 * @type Number
 */

/**
 * The origin point of the sprite. (0,0) to (1,1).
 *
 * @property anchor
 * @type Point
 */

/**
 * The scale factor of the sprite.
 *
 * @property scale
 * @type Point
 */

/**
 * The rotation of the sprite in radians.
 *
 * @property rotation
 * @type Number
 */

/**
 * The blend mode to be applied to the sprite.
 *
 * @property blendMode
 * @type Number
 */

/**
 * Sets the filters for the sprite.
 *
 * @property filters
 * @type Array
 */

/**
 * [read-only] The array of children of the sprite.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the sprite.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The tilemap which displays 2D tile-based game map.
 *
 * @class Tilemap
 * @constructor
 */
function Tilemap() {
  this.initialize.apply(this, arguments);
}

Tilemap.prototype = Object.create(PIXI.Container.prototype);
Tilemap.prototype.constructor = Tilemap;

Tilemap.prototype.initialize = function () {
  PIXI.Container.call(this);

  this._margin = 20;
  this._width = Graphics.width + this._margin * 2;
  this._height = Graphics.height + this._margin * 2;
  this._tileWidth = 48;
  this._tileHeight = 48;
  this._mapWidth = 0;
  this._mapHeight = 0;
  this._mapData = null;
  this._layerWidth = 0;
  this._layerHeight = 0;
  this._lastTiles = [];

  /**
   * The bitmaps used as a tileset.
   *
   * @property bitmaps
   * @type Array
   */
  this.bitmaps = [];

  /**
   * The origin point of the tilemap for scrolling.
   *
   * @property origin
   * @type Point
   */
  this.origin = new Point();

  /**
   * The tileset flags.
   *
   * @property flags
   * @type Array
   */
  this.flags = [];

  /**
   * The animation count for autotiles.
   *
   * @property animationCount
   * @type Number
   */
  this.animationCount = 0;

  /**
   * Whether the tilemap loops horizontal.
   *
   * @property horizontalWrap
   * @type Boolean
   */
  this.horizontalWrap = false;

  /**
   * Whether the tilemap loops vertical.
   *
   * @property verticalWrap
   * @type Boolean
   */
  this.verticalWrap = false;

  this._createLayers();
  this.refresh();
};

/**
 * The width of the screen in pixels.
 *
 * @property width
 * @type Number
 */
Object.defineProperty(Tilemap.prototype, "width", {
  get: function () {
    return this._width;
  },
  set: function (value) {
    if (this._width !== value) {
      this._width = value;
      this._createLayers();
    }
  },
});

/**
 * The height of the screen in pixels.
 *
 * @property height
 * @type Number
 */
Object.defineProperty(Tilemap.prototype, "height", {
  get: function () {
    return this._height;
  },
  set: function (value) {
    if (this._height !== value) {
      this._height = value;
      this._createLayers();
    }
  },
});

/**
 * The width of a tile in pixels.
 *
 * @property tileWidth
 * @type Number
 */
Object.defineProperty(Tilemap.prototype, "tileWidth", {
  get: function () {
    return this._tileWidth;
  },
  set: function (value) {
    if (this._tileWidth !== value) {
      this._tileWidth = value;
      this._createLayers();
    }
  },
});

/**
 * The height of a tile in pixels.
 *
 * @property tileHeight
 * @type Number
 */
Object.defineProperty(Tilemap.prototype, "tileHeight", {
  get: function () {
    return this._tileHeight;
  },
  set: function (value) {
    if (this._tileHeight !== value) {
      this._tileHeight = value;
      this._createLayers();
    }
  },
});

/**
 * Sets the tilemap data.
 *
 * @method setData
 * @param {Number} width The width of the map in number of tiles
 * @param {Number} height The height of the map in number of tiles
 * @param {Array} data The one dimensional array for the map data
 */
Tilemap.prototype.setData = function (width, height, data) {
  this._mapWidth = width;
  this._mapHeight = height;
  this._mapData = data;
};

/**
 * Checks whether the tileset is ready to render.
 *
 * @method isReady
 * @type Boolean
 * @return {Boolean} True if the tilemap is ready
 */
Tilemap.prototype.isReady = function () {
  for (var i = 0; i < this.bitmaps.length; i++) {
    if (this.bitmaps[i] && !this.bitmaps[i].isReady()) {
      return false;
    }
  }
  return true;
};

/**
 * Updates the tilemap for each frame.
 *
 * @method update
 */
Tilemap.prototype.update = function () {
  this.animationCount++;
  this.animationFrame = Math.floor(this.animationCount / 30);
  this.children.forEach(function (child) {
    if (child.update) {
      child.update();
    }
  });
  for (var i = 0; i < this.bitmaps.length; i++) {
    if (this.bitmaps[i]) {
      this.bitmaps[i].touch();
    }
  }
};

/**
 * Forces to repaint the entire tilemap.
 *
 * @method refresh
 */
Tilemap.prototype.refresh = function () {
  this._lastTiles.length = 0;
};

/**
 * Forces to refresh the tileset
 *
 * @method refresh
 */
Tilemap.prototype.refreshTileset = function () {};

/**
 * @method updateTransform
 * @private
 */
Tilemap.prototype.updateTransform = function () {
  var ox = Math.floor(this.origin.x);
  var oy = Math.floor(this.origin.y);
  var startX = Math.floor((ox - this._margin) / this._tileWidth);
  var startY = Math.floor((oy - this._margin) / this._tileHeight);
  this._updateLayerPositions(startX, startY);
  if (
    this._needsRepaint ||
    this._lastAnimationFrame !== this.animationFrame ||
    this._lastStartX !== startX ||
    this._lastStartY !== startY
  ) {
    this._frameUpdated = this._lastAnimationFrame !== this.animationFrame;
    this._lastAnimationFrame = this.animationFrame;
    this._lastStartX = startX;
    this._lastStartY = startY;
    this._paintAllTiles(startX, startY);
    this._needsRepaint = false;
  }
  this._sortChildren();
  PIXI.Container.prototype.updateTransform.call(this);
};

/**
 * @method _createLayers
 * @private
 */
Tilemap.prototype._createLayers = function () {
  var width = this._width;
  var height = this._height;
  var margin = this._margin;
  var tileCols = Math.ceil(width / this._tileWidth) + 1;
  var tileRows = Math.ceil(height / this._tileHeight) + 1;
  var layerWidth = tileCols * this._tileWidth;
  var layerHeight = tileRows * this._tileHeight;
  this._lowerBitmap = new Bitmap(layerWidth, layerHeight);
  this._upperBitmap = new Bitmap(layerWidth, layerHeight);
  this._layerWidth = layerWidth;
  this._layerHeight = layerHeight;

  /*
   * Z coordinate:
   *
   * 0 : Lower tiles
   * 1 : Lower characters
   * 3 : Normal characters
   * 4 : Upper tiles
   * 5 : Upper characters
   * 6 : Airship shadow
   * 7 : Balloon
   * 8 : Animation
   * 9 : Destination
   */

  this._lowerLayer = new Sprite();
  this._lowerLayer.move(-margin, -margin, width, height);
  this._lowerLayer.z = 0;

  this._upperLayer = new Sprite();
  this._upperLayer.move(-margin, -margin, width, height);
  this._upperLayer.z = 4;

  for (var i = 0; i < 4; i++) {
    this._lowerLayer.addChild(new Sprite(this._lowerBitmap));
    this._upperLayer.addChild(new Sprite(this._upperBitmap));
  }

  this.addChild(this._lowerLayer);
  this.addChild(this._upperLayer);
};

/**
 * @method _updateLayerPositions
 * @param {Number} startX
 * @param {Number} startY
 * @private
 */
Tilemap.prototype._updateLayerPositions = function (startX, startY) {
  var m = this._margin;
  var ox = Math.floor(this.origin.x);
  var oy = Math.floor(this.origin.y);
  var x2 = (ox - m).mod(this._layerWidth);
  var y2 = (oy - m).mod(this._layerHeight);
  var w1 = this._layerWidth - x2;
  var h1 = this._layerHeight - y2;
  var w2 = this._width - w1;
  var h2 = this._height - h1;

  for (var i = 0; i < 2; i++) {
    var children;
    if (i === 0) {
      children = this._lowerLayer.children;
    } else {
      children = this._upperLayer.children;
    }
    children[0].move(0, 0, w1, h1);
    children[0].setFrame(x2, y2, w1, h1);
    children[1].move(w1, 0, w2, h1);
    children[1].setFrame(0, y2, w2, h1);
    children[2].move(0, h1, w1, h2);
    children[2].setFrame(x2, 0, w1, h2);
    children[3].move(w1, h1, w2, h2);
    children[3].setFrame(0, 0, w2, h2);
  }
};

/**
 * @method _paintAllTiles
 * @param {Number} startX
 * @param {Number} startY
 * @private
 */
Tilemap.prototype._paintAllTiles = function (startX, startY) {
  var tileCols = Math.ceil(this._width / this._tileWidth) + 1;
  var tileRows = Math.ceil(this._height / this._tileHeight) + 1;
  for (var y = 0; y < tileRows; y++) {
    for (var x = 0; x < tileCols; x++) {
      this._paintTiles(startX, startY, x, y);
    }
  }
};

/**
 * @method _paintTiles
 * @param {Number} startX
 * @param {Number} startY
 * @param {Number} x
 * @param {Number} y
 * @private
 */
Tilemap.prototype._paintTiles = function (startX, startY, x, y) {
  var tableEdgeVirtualId = 10000;
  var mx = startX + x;
  var my = startY + y;
  var dx = (mx * this._tileWidth).mod(this._layerWidth);
  var dy = (my * this._tileHeight).mod(this._layerHeight);
  var lx = dx / this._tileWidth;
  var ly = dy / this._tileHeight;
  var tileId0 = this._readMapData(mx, my, 0);
  var tileId1 = this._readMapData(mx, my, 1);
  var tileId2 = this._readMapData(mx, my, 2);
  var tileId3 = this._readMapData(mx, my, 3);
  var shadowBits = this._readMapData(mx, my, 4);
  var upperTileId1 = this._readMapData(mx, my - 1, 1);
  var lowerTiles = [];
  var upperTiles = [];

  if (this._isHigherTile(tileId0)) {
    upperTiles.push(tileId0);
  } else {
    lowerTiles.push(tileId0);
  }
  if (this._isHigherTile(tileId1)) {
    upperTiles.push(tileId1);
  } else {
    lowerTiles.push(tileId1);
  }

  lowerTiles.push(-shadowBits);

  if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
    if (!Tilemap.isShadowingTile(tileId0)) {
      lowerTiles.push(tableEdgeVirtualId + upperTileId1);
    }
  }

  if (this._isOverpassPosition(mx, my)) {
    upperTiles.push(tileId2);
    upperTiles.push(tileId3);
  } else {
    if (this._isHigherTile(tileId2)) {
      upperTiles.push(tileId2);
    } else {
      lowerTiles.push(tileId2);
    }
    if (this._isHigherTile(tileId3)) {
      upperTiles.push(tileId3);
    } else {
      lowerTiles.push(tileId3);
    }
  }

  var lastLowerTiles = this._readLastTiles(0, lx, ly);
  if (
    !lowerTiles.equals(lastLowerTiles) ||
    (Tilemap.isTileA1(tileId0) && this._frameUpdated)
  ) {
    this._lowerBitmap.clearRect(dx, dy, this._tileWidth, this._tileHeight);
    for (var i = 0; i < lowerTiles.length; i++) {
      var lowerTileId = lowerTiles[i];
      if (lowerTileId < 0) {
        this._drawShadow(this._lowerBitmap, shadowBits, dx, dy);
      } else if (lowerTileId >= tableEdgeVirtualId) {
        this._drawTableEdge(this._lowerBitmap, upperTileId1, dx, dy);
      } else {
        this._drawTile(this._lowerBitmap, lowerTileId, dx, dy);
      }
    }
    this._writeLastTiles(0, lx, ly, lowerTiles);
  }

  var lastUpperTiles = this._readLastTiles(1, lx, ly);
  if (!upperTiles.equals(lastUpperTiles)) {
    this._upperBitmap.clearRect(dx, dy, this._tileWidth, this._tileHeight);
    for (var j = 0; j < upperTiles.length; j++) {
      this._drawTile(this._upperBitmap, upperTiles[j], dx, dy);
    }
    this._writeLastTiles(1, lx, ly, upperTiles);
  }
};

/**
 * @method _readLastTiles
 * @param {Number} i
 * @param {Number} x
 * @param {Number} y
 * @private
 */
Tilemap.prototype._readLastTiles = function (i, x, y) {
  var array1 = this._lastTiles[i];
  if (array1) {
    var array2 = array1[y];
    if (array2) {
      var tiles = array2[x];
      if (tiles) {
        return tiles;
      }
    }
  }
  return [];
};

/**
 * @method _writeLastTiles
 * @param {Number} i
 * @param {Number} x
 * @param {Number} y
 * @param {Array} tiles
 * @private
 */
Tilemap.prototype._writeLastTiles = function (i, x, y, tiles) {
  var array1 = this._lastTiles[i];
  if (!array1) {
    array1 = this._lastTiles[i] = [];
  }
  var array2 = array1[y];
  if (!array2) {
    array2 = array1[y] = [];
  }
  array2[x] = tiles;
};

/**
 * @method _drawTile
 * @param {Bitmap} bitmap
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
Tilemap.prototype._drawTile = function (bitmap, tileId, dx, dy) {
  if (Tilemap.isVisibleTile(tileId)) {
    if (Tilemap.isAutotile(tileId)) {
      this._drawAutotile(bitmap, tileId, dx, dy);
    } else {
      this._drawNormalTile(bitmap, tileId, dx, dy);
    }
  }
};

/**
 * @method _drawNormalTile
 * @param {Bitmap} bitmap
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
Tilemap.prototype._drawNormalTile = function (bitmap, tileId, dx, dy) {
  var setNumber = 0;

  if (Tilemap.isTileA5(tileId)) {
    setNumber = 4;
  } else {
    setNumber = 5 + Math.floor(tileId / 256);
  }

  var w = this._tileWidth;
  var h = this._tileHeight;
  var sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * w;
  var sy = (Math.floor((tileId % 256) / 8) % 16) * h;

  var source = this.bitmaps[setNumber];
  if (source) {
    bitmap.bltImage(source, sx, sy, w, h, dx, dy, w, h);
  }
};

/**
 * @method _drawAutotile
 * @param {Bitmap} bitmap
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
Tilemap.prototype._drawAutotile = function (bitmap, tileId, dx, dy) {
  var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
  var kind = Tilemap.getAutotileKind(tileId);
  var shape = Tilemap.getAutotileShape(tileId);
  var tx = kind % 8;
  var ty = Math.floor(kind / 8);
  var bx = 0;
  var by = 0;
  var setNumber = 0;
  var isTable = false;

  if (Tilemap.isTileA1(tileId)) {
    var waterSurfaceIndex = [0, 1, 2, 1][this.animationFrame % 4];
    setNumber = 0;
    if (kind === 0) {
      bx = waterSurfaceIndex * 2;
      by = 0;
    } else if (kind === 1) {
      bx = waterSurfaceIndex * 2;
      by = 3;
    } else if (kind === 2) {
      bx = 6;
      by = 0;
    } else if (kind === 3) {
      bx = 6;
      by = 3;
    } else {
      bx = Math.floor(tx / 4) * 8;
      by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;
      if (kind % 2 === 0) {
        bx += waterSurfaceIndex * 2;
      } else {
        bx += 6;
        autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
        by += this.animationFrame % 3;
      }
    }
  } else if (Tilemap.isTileA2(tileId)) {
    setNumber = 1;
    bx = tx * 2;
    by = (ty - 2) * 3;
    isTable = this._isTableTile(tileId);
  } else if (Tilemap.isTileA3(tileId)) {
    setNumber = 2;
    bx = tx * 2;
    by = (ty - 6) * 2;
    autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
  } else if (Tilemap.isTileA4(tileId)) {
    setNumber = 3;
    bx = tx * 2;
    by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
    if (ty % 2 === 1) {
      autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
    }
  }

  var table = autotileTable[shape];
  var source = this.bitmaps[setNumber];

  if (table && source) {
    var w1 = this._tileWidth / 2;
    var h1 = this._tileHeight / 2;
    for (var i = 0; i < 4; i++) {
      var qsx = table[i][0];
      var qsy = table[i][1];
      var sx1 = (bx * 2 + qsx) * w1;
      var sy1 = (by * 2 + qsy) * h1;
      var dx1 = dx + (i % 2) * w1;
      var dy1 = dy + Math.floor(i / 2) * h1;
      if (isTable && (qsy === 1 || qsy === 5)) {
        var qsx2 = qsx;
        var qsy2 = 3;
        if (qsy === 1) {
          qsx2 = [0, 3, 2, 1][qsx];
        }
        var sx2 = (bx * 2 + qsx2) * w1;
        var sy2 = (by * 2 + qsy2) * h1;
        bitmap.bltImage(source, sx2, sy2, w1, h1, dx1, dy1, w1, h1);
        dy1 += h1 / 2;
        bitmap.bltImage(source, sx1, sy1, w1, h1 / 2, dx1, dy1, w1, h1 / 2);
      } else {
        bitmap.bltImage(source, sx1, sy1, w1, h1, dx1, dy1, w1, h1);
      }
    }
  }
};

/**
 * @method _drawTableEdge
 * @param {Bitmap} bitmap
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
Tilemap.prototype._drawTableEdge = function (bitmap, tileId, dx, dy) {
  if (Tilemap.isTileA2(tileId)) {
    var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
    var kind = Tilemap.getAutotileKind(tileId);
    var shape = Tilemap.getAutotileShape(tileId);
    var tx = kind % 8;
    var ty = Math.floor(kind / 8);
    var setNumber = 1;
    var bx = tx * 2;
    var by = (ty - 2) * 3;
    var table = autotileTable[shape];

    if (table) {
      var source = this.bitmaps[setNumber];
      var w1 = this._tileWidth / 2;
      var h1 = this._tileHeight / 2;
      for (var i = 0; i < 2; i++) {
        var qsx = table[2 + i][0];
        var qsy = table[2 + i][1];
        var sx1 = (bx * 2 + qsx) * w1;
        var sy1 = (by * 2 + qsy) * h1 + h1 / 2;
        var dx1 = dx + (i % 2) * w1;
        var dy1 = dy + Math.floor(i / 2) * h1;
        bitmap.bltImage(source, sx1, sy1, w1, h1 / 2, dx1, dy1, w1, h1 / 2);
      }
    }
  }
};

/**
 * @method _drawShadow
 * @param {Bitmap} bitmap
 * @param {Number} shadowBits
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
Tilemap.prototype._drawShadow = function (bitmap, shadowBits, dx, dy) {
  if (shadowBits & 0x0f) {
    var w1 = this._tileWidth / 2;
    var h1 = this._tileHeight / 2;
    var color = "rgba(0,0,0,0.5)";
    for (var i = 0; i < 4; i++) {
      if (shadowBits & (1 << i)) {
        var dx1 = dx + (i % 2) * w1;
        var dy1 = dy + Math.floor(i / 2) * h1;
        bitmap.fillRect(dx1, dy1, w1, h1, color);
      }
    }
  }
};

/**
 * @method _readMapData
 * @param {Number} x
 * @param {Number} y
 * @param {Number} z
 * @return {Number}
 * @private
 */
Tilemap.prototype._readMapData = function (x, y, z) {
  if (this._mapData) {
    var width = this._mapWidth;
    var height = this._mapHeight;
    if (this.horizontalWrap) {
      x = x.mod(width);
    }
    if (this.verticalWrap) {
      y = y.mod(height);
    }
    if (x >= 0 && x < width && y >= 0 && y < height) {
      return this._mapData[(z * height + y) * width + x] || 0;
    } else {
      return 0;
    }
  } else {
    return 0;
  }
};

/**
 * @method _isHigherTile
 * @param {Number} tileId
 * @return {Boolean}
 * @private
 */
Tilemap.prototype._isHigherTile = function (tileId) {
  return this.flags[tileId] & 0x10;
};

/**
 * @method _isTableTile
 * @param {Number} tileId
 * @return {Boolean}
 * @private
 */
Tilemap.prototype._isTableTile = function (tileId) {
  return Tilemap.isTileA2(tileId) && this.flags[tileId] & 0x80;
};

/**
 * @method _isOverpassPosition
 * @param {Number} mx
 * @param {Number} my
 * @return {Boolean}
 * @private
 */
Tilemap.prototype._isOverpassPosition = function (mx, my) {
  return false;
};

/**
 * @method _sortChildren
 * @private
 */
Tilemap.prototype._sortChildren = function () {
  this.children.sort(this._compareChildOrder.bind(this));
};

/**
 * @method _compareChildOrder
 * @param {Object} a
 * @param {Object} b
 * @private
 */
Tilemap.prototype._compareChildOrder = function (a, b) {
  if (a.z !== b.z) {
    return a.z - b.z;
  } else if (a.y !== b.y) {
    return a.y - b.y;
  } else {
    return a.spriteId - b.spriteId;
  }
};

// Tile type checkers

Tilemap.TILE_ID_B = 0;
Tilemap.TILE_ID_C = 256;
Tilemap.TILE_ID_D = 512;
Tilemap.TILE_ID_E = 768;
Tilemap.TILE_ID_A5 = 1536;
Tilemap.TILE_ID_A1 = 2048;
Tilemap.TILE_ID_A2 = 2816;
Tilemap.TILE_ID_A3 = 4352;
Tilemap.TILE_ID_A4 = 5888;
Tilemap.TILE_ID_MAX = 8192;

Tilemap.isVisibleTile = function (tileId) {
  return tileId > 0 && tileId < this.TILE_ID_MAX;
};

Tilemap.isAutotile = function (tileId) {
  return tileId >= this.TILE_ID_A1;
};

Tilemap.getAutotileKind = function (tileId) {
  return Math.floor((tileId - this.TILE_ID_A1) / 48);
};

Tilemap.getAutotileShape = function (tileId) {
  return (tileId - this.TILE_ID_A1) % 48;
};

Tilemap.makeAutotileId = function (kind, shape) {
  return this.TILE_ID_A1 + kind * 48 + shape;
};

Tilemap.isSameKindTile = function (tileID1, tileID2) {
  if (this.isAutotile(tileID1) && this.isAutotile(tileID2)) {
    return this.getAutotileKind(tileID1) === this.getAutotileKind(tileID2);
  } else {
    return tileID1 === tileID2;
  }
};

Tilemap.isTileA1 = function (tileId) {
  return tileId >= this.TILE_ID_A1 && tileId < this.TILE_ID_A2;
};

Tilemap.isTileA2 = function (tileId) {
  return tileId >= this.TILE_ID_A2 && tileId < this.TILE_ID_A3;
};

Tilemap.isTileA3 = function (tileId) {
  return tileId >= this.TILE_ID_A3 && tileId < this.TILE_ID_A4;
};

Tilemap.isTileA4 = function (tileId) {
  return tileId >= this.TILE_ID_A4 && tileId < this.TILE_ID_MAX;
};

Tilemap.isTileA5 = function (tileId) {
  return tileId >= this.TILE_ID_A5 && tileId < this.TILE_ID_A1;
};

Tilemap.isWaterTile = function (tileId) {
  if (this.isTileA1(tileId)) {
    return !(tileId >= this.TILE_ID_A1 + 96 && tileId < this.TILE_ID_A1 + 192);
  } else {
    return false;
  }
};

Tilemap.isWaterfallTile = function (tileId) {
  if (tileId >= this.TILE_ID_A1 + 192 && tileId < this.TILE_ID_A2) {
    return this.getAutotileKind(tileId) % 2 === 1;
  } else {
    return false;
  }
};

Tilemap.isGroundTile = function (tileId) {
  return (
    this.isTileA1(tileId) || this.isTileA2(tileId) || this.isTileA5(tileId)
  );
};

Tilemap.isShadowingTile = function (tileId) {
  return this.isTileA3(tileId) || this.isTileA4(tileId);
};

Tilemap.isRoofTile = function (tileId) {
  return this.isTileA3(tileId) && this.getAutotileKind(tileId) % 16 < 8;
};

Tilemap.isWallTopTile = function (tileId) {
  return this.isTileA4(tileId) && this.getAutotileKind(tileId) % 16 < 8;
};

Tilemap.isWallSideTile = function (tileId) {
  return (
    (this.isTileA3(tileId) || this.isTileA4(tileId)) &&
    this.getAutotileKind(tileId) % 16 >= 8
  );
};

Tilemap.isWallTile = function (tileId) {
  return this.isWallTopTile(tileId) || this.isWallSideTile(tileId);
};

Tilemap.isFloorTypeAutotile = function (tileId) {
  return (
    (this.isTileA1(tileId) && !this.isWaterfallTile(tileId)) ||
    this.isTileA2(tileId) ||
    this.isWallTopTile(tileId)
  );
};

Tilemap.isWallTypeAutotile = function (tileId) {
  return this.isRoofTile(tileId) || this.isWallSideTile(tileId);
};

Tilemap.isWaterfallTypeAutotile = function (tileId) {
  return this.isWaterfallTile(tileId);
};

// Autotile shape number to coordinates of tileset images

Tilemap.FLOOR_AUTOTILE_TABLE = [
  [
    [2, 4],
    [1, 4],
    [2, 3],
    [1, 3],
  ],
  [
    [2, 0],
    [1, 4],
    [2, 3],
    [1, 3],
  ],
  [
    [2, 4],
    [3, 0],
    [2, 3],
    [1, 3],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 3],
    [1, 3],
  ],
  [
    [2, 4],
    [1, 4],
    [2, 3],
    [3, 1],
  ],
  [
    [2, 0],
    [1, 4],
    [2, 3],
    [3, 1],
  ],
  [
    [2, 4],
    [3, 0],
    [2, 3],
    [3, 1],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 3],
    [3, 1],
  ],
  [
    [2, 4],
    [1, 4],
    [2, 1],
    [1, 3],
  ],
  [
    [2, 0],
    [1, 4],
    [2, 1],
    [1, 3],
  ],
  [
    [2, 4],
    [3, 0],
    [2, 1],
    [1, 3],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 1],
    [1, 3],
  ],
  [
    [2, 4],
    [1, 4],
    [2, 1],
    [3, 1],
  ],
  [
    [2, 0],
    [1, 4],
    [2, 1],
    [3, 1],
  ],
  [
    [2, 4],
    [3, 0],
    [2, 1],
    [3, 1],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 1],
    [3, 1],
  ],
  [
    [0, 4],
    [1, 4],
    [0, 3],
    [1, 3],
  ],
  [
    [0, 4],
    [3, 0],
    [0, 3],
    [1, 3],
  ],
  [
    [0, 4],
    [1, 4],
    [0, 3],
    [3, 1],
  ],
  [
    [0, 4],
    [3, 0],
    [0, 3],
    [3, 1],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 3],
    [1, 3],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 3],
    [3, 1],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 1],
    [1, 3],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 1],
    [3, 1],
  ],
  [
    [2, 4],
    [3, 4],
    [2, 3],
    [3, 3],
  ],
  [
    [2, 4],
    [3, 4],
    [2, 1],
    [3, 3],
  ],
  [
    [2, 0],
    [3, 4],
    [2, 3],
    [3, 3],
  ],
  [
    [2, 0],
    [3, 4],
    [2, 1],
    [3, 3],
  ],
  [
    [2, 4],
    [1, 4],
    [2, 5],
    [1, 5],
  ],
  [
    [2, 0],
    [1, 4],
    [2, 5],
    [1, 5],
  ],
  [
    [2, 4],
    [3, 0],
    [2, 5],
    [1, 5],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 5],
    [1, 5],
  ],
  [
    [0, 4],
    [3, 4],
    [0, 3],
    [3, 3],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 5],
    [1, 5],
  ],
  [
    [0, 2],
    [1, 2],
    [0, 3],
    [1, 3],
  ],
  [
    [0, 2],
    [1, 2],
    [0, 3],
    [3, 1],
  ],
  [
    [2, 2],
    [3, 2],
    [2, 3],
    [3, 3],
  ],
  [
    [2, 2],
    [3, 2],
    [2, 1],
    [3, 3],
  ],
  [
    [2, 4],
    [3, 4],
    [2, 5],
    [3, 5],
  ],
  [
    [2, 0],
    [3, 4],
    [2, 5],
    [3, 5],
  ],
  [
    [0, 4],
    [1, 4],
    [0, 5],
    [1, 5],
  ],
  [
    [0, 4],
    [3, 0],
    [0, 5],
    [1, 5],
  ],
  [
    [0, 2],
    [3, 2],
    [0, 3],
    [3, 3],
  ],
  [
    [0, 2],
    [1, 2],
    [0, 5],
    [1, 5],
  ],
  [
    [0, 4],
    [3, 4],
    [0, 5],
    [3, 5],
  ],
  [
    [2, 2],
    [3, 2],
    [2, 5],
    [3, 5],
  ],
  [
    [0, 2],
    [3, 2],
    [0, 5],
    [3, 5],
  ],
  [
    [0, 0],
    [1, 0],
    [0, 1],
    [1, 1],
  ],
];

Tilemap.WALL_AUTOTILE_TABLE = [
  [
    [2, 2],
    [1, 2],
    [2, 1],
    [1, 1],
  ],
  [
    [0, 2],
    [1, 2],
    [0, 1],
    [1, 1],
  ],
  [
    [2, 0],
    [1, 0],
    [2, 1],
    [1, 1],
  ],
  [
    [0, 0],
    [1, 0],
    [0, 1],
    [1, 1],
  ],
  [
    [2, 2],
    [3, 2],
    [2, 1],
    [3, 1],
  ],
  [
    [0, 2],
    [3, 2],
    [0, 1],
    [3, 1],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 1],
    [3, 1],
  ],
  [
    [0, 0],
    [3, 0],
    [0, 1],
    [3, 1],
  ],
  [
    [2, 2],
    [1, 2],
    [2, 3],
    [1, 3],
  ],
  [
    [0, 2],
    [1, 2],
    [0, 3],
    [1, 3],
  ],
  [
    [2, 0],
    [1, 0],
    [2, 3],
    [1, 3],
  ],
  [
    [0, 0],
    [1, 0],
    [0, 3],
    [1, 3],
  ],
  [
    [2, 2],
    [3, 2],
    [2, 3],
    [3, 3],
  ],
  [
    [0, 2],
    [3, 2],
    [0, 3],
    [3, 3],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 3],
    [3, 3],
  ],
  [
    [0, 0],
    [3, 0],
    [0, 3],
    [3, 3],
  ],
];

Tilemap.WATERFALL_AUTOTILE_TABLE = [
  [
    [2, 0],
    [1, 0],
    [2, 1],
    [1, 1],
  ],
  [
    [0, 0],
    [1, 0],
    [0, 1],
    [1, 1],
  ],
  [
    [2, 0],
    [3, 0],
    [2, 1],
    [3, 1],
  ],
  [
    [0, 0],
    [3, 0],
    [0, 1],
    [3, 1],
  ],
];

// The important members from Pixi.js

/**
 * [read-only] The array of children of the tilemap.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the tilemap.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The tilemap which displays 2D tile-based game map using shaders
 *
 * @class Tilemap
 * @constructor
 */
function ShaderTilemap() {
  Tilemap.apply(this, arguments);
  this.roundPixels = true;
}

ShaderTilemap.prototype = Object.create(Tilemap.prototype);
ShaderTilemap.prototype.constructor = ShaderTilemap;

// we need this constant for some platforms (Samsung S4, S5, Tab4, HTC One H8)
PIXI.glCore.VertexArrayObject.FORCE_NATIVE = true;
PIXI.settings.GC_MODE = PIXI.GC_MODES.AUTO;
PIXI.tilemap.TileRenderer.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
PIXI.tilemap.TileRenderer.DO_CLEAR = true;

/**
 * Uploads animation state in renderer
 *
 * @method _hackRenderer
 * @private
 */
ShaderTilemap.prototype._hackRenderer = function (renderer) {
  var af = this.animationFrame % 4;
  if (af == 3) af = 1;
  renderer.plugins.tilemap.tileAnim[0] = af * this._tileWidth;
  renderer.plugins.tilemap.tileAnim[1] =
    (this.animationFrame % 3) * this._tileHeight;
  return renderer;
};

/**
 * PIXI render method
 *
 * @method renderCanvas
 * @param {Object} pixi renderer
 */
ShaderTilemap.prototype.renderCanvas = function (renderer) {
  this._hackRenderer(renderer);
  PIXI.Container.prototype.renderCanvas.call(this, renderer);
};

/**
 * PIXI render method
 *
 * @method renderWebGL
 * @param {Object} pixi renderer
 */
ShaderTilemap.prototype.renderWebGL = function (renderer) {
  this._hackRenderer(renderer);
  PIXI.Container.prototype.renderWebGL.call(this, renderer);
};

/**
 * Forces to repaint the entire tilemap AND update bitmaps list if needed
 *
 * @method refresh
 */
ShaderTilemap.prototype.refresh = function () {
  if (this._lastBitmapLength !== this.bitmaps.length) {
    this._lastBitmapLength = this.bitmaps.length;
    this.refreshTileset();
  }
  this._needsRepaint = true;
};

/**
 * Call after you update tileset
 *
 * @method updateBitmaps
 */
ShaderTilemap.prototype.refreshTileset = function () {
  var bitmaps = this.bitmaps.map(function (x) {
    return x._baseTexture ? new PIXI.Texture(x._baseTexture) : x;
  });
  this.lowerLayer.setBitmaps(bitmaps);
  this.upperLayer.setBitmaps(bitmaps);
};

/**
 * @method updateTransform
 * @private
 */
ShaderTilemap.prototype.updateTransform = function () {
  if (this.roundPixels) {
    var ox = Math.floor(this.origin.x);
    var oy = Math.floor(this.origin.y);
  } else {
    ox = this.origin.x;
    oy = this.origin.y;
  }
  var startX = Math.floor((ox - this._margin) / this._tileWidth);
  var startY = Math.floor((oy - this._margin) / this._tileHeight);
  this._updateLayerPositions(startX, startY);
  if (
    this._needsRepaint ||
    this._lastStartX !== startX ||
    this._lastStartY !== startY
  ) {
    this._lastStartX = startX;
    this._lastStartY = startY;
    this._paintAllTiles(startX, startY);
    this._needsRepaint = false;
  }
  this._sortChildren();
  PIXI.Container.prototype.updateTransform.call(this);
};

/**
 * @method _createLayers
 * @private
 */
ShaderTilemap.prototype._createLayers = function () {
  var width = this._width;
  var height = this._height;
  var margin = this._margin;
  var tileCols = Math.ceil(width / this._tileWidth) + 1;
  var tileRows = Math.ceil(height / this._tileHeight) + 1;
  var layerWidth = (this._layerWidth = tileCols * this._tileWidth);
  var layerHeight = (this._layerHeight = tileRows * this._tileHeight);
  this._needsRepaint = true;

  if (!this.lowerZLayer) {
    //@hackerham: create layers only in initialization. Doesn't depend on width/height
    this.addChild((this.lowerZLayer = new PIXI.tilemap.ZLayer(this, 0)));
    this.addChild((this.upperZLayer = new PIXI.tilemap.ZLayer(this, 4)));

    var parameters = PluginManager.parameters("ShaderTilemap");
    var useSquareShader = Number(
      parameters.hasOwnProperty("squareShader") ? parameters["squareShader"] : 0
    );

    this.lowerZLayer.addChild(
      (this.lowerLayer = new PIXI.tilemap.CompositeRectTileLayer(
        0,
        [],
        useSquareShader
      ))
    );
    this.lowerLayer.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);
    this.upperZLayer.addChild(
      (this.upperLayer = new PIXI.tilemap.CompositeRectTileLayer(
        4,
        [],
        useSquareShader
      ))
    );
  }
};

/**
 * @method _updateLayerPositions
 * @param {Number} startX
 * @param {Number} startY
 * @private
 */
ShaderTilemap.prototype._updateLayerPositions = function (startX, startY) {
  if (this.roundPixels) {
    var ox = Math.floor(this.origin.x);
    var oy = Math.floor(this.origin.y);
  } else {
    ox = this.origin.x;
    oy = this.origin.y;
  }
  this.lowerZLayer.position.x = startX * this._tileWidth - ox;
  this.lowerZLayer.position.y = startY * this._tileHeight - oy;
  this.upperZLayer.position.x = startX * this._tileWidth - ox;
  this.upperZLayer.position.y = startY * this._tileHeight - oy;
};

/**
 * @method _paintAllTiles
 * @param {Number} startX
 * @param {Number} startY
 * @private
 */
ShaderTilemap.prototype._paintAllTiles = function (startX, startY) {
  this.lowerZLayer.clear();
  this.upperZLayer.clear();
  var tileCols = Math.ceil(this._width / this._tileWidth) + 1;
  var tileRows = Math.ceil(this._height / this._tileHeight) + 1;
  for (var y = 0; y < tileRows; y++) {
    for (var x = 0; x < tileCols; x++) {
      this._paintTiles(startX, startY, x, y);
    }
  }
};

/**
 * @method _paintTiles
 * @param {Number} startX
 * @param {Number} startY
 * @param {Number} x
 * @param {Number} y
 * @private
 */
ShaderTilemap.prototype._paintTiles = function (startX, startY, x, y) {
  var mx = startX + x;
  var my = startY + y;
  var dx = x * this._tileWidth,
    dy = y * this._tileHeight;
  var tileId0 = this._readMapData(mx, my, 0);
  var tileId1 = this._readMapData(mx, my, 1);
  var tileId2 = this._readMapData(mx, my, 2);
  var tileId3 = this._readMapData(mx, my, 3);
  var shadowBits = this._readMapData(mx, my, 4);
  var upperTileId1 = this._readMapData(mx, my - 1, 1);
  var lowerLayer = this.lowerLayer.children[0];
  var upperLayer = this.upperLayer.children[0];

  if (this._isHigherTile(tileId0)) {
    this._drawTile(upperLayer, tileId0, dx, dy);
  } else {
    this._drawTile(lowerLayer, tileId0, dx, dy);
  }
  if (this._isHigherTile(tileId1)) {
    this._drawTile(upperLayer, tileId1, dx, dy);
  } else {
    this._drawTile(lowerLayer, tileId1, dx, dy);
  }

  this._drawShadow(lowerLayer, shadowBits, dx, dy);
  if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
    if (!Tilemap.isShadowingTile(tileId0)) {
      this._drawTableEdge(lowerLayer, upperTileId1, dx, dy);
    }
  }

  if (this._isOverpassPosition(mx, my)) {
    this._drawTile(upperLayer, tileId2, dx, dy);
    this._drawTile(upperLayer, tileId3, dx, dy);
  } else {
    if (this._isHigherTile(tileId2)) {
      this._drawTile(upperLayer, tileId2, dx, dy);
    } else {
      this._drawTile(lowerLayer, tileId2, dx, dy);
    }
    if (this._isHigherTile(tileId3)) {
      this._drawTile(upperLayer, tileId3, dx, dy);
    } else {
      this._drawTile(lowerLayer, tileId3, dx, dy);
    }
  }
};

/**
 * @method _drawTile
 * @param {Array} layers
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
ShaderTilemap.prototype._drawTile = function (layer, tileId, dx, dy) {
  if (Tilemap.isVisibleTile(tileId)) {
    if (Tilemap.isAutotile(tileId)) {
      this._drawAutotile(layer, tileId, dx, dy);
    } else {
      this._drawNormalTile(layer, tileId, dx, dy);
    }
  }
};

/**
 * @method _drawNormalTile
 * @param {Array} layers
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
ShaderTilemap.prototype._drawNormalTile = function (layer, tileId, dx, dy) {
  var setNumber = 0;

  if (Tilemap.isTileA5(tileId)) {
    setNumber = 4;
  } else {
    setNumber = 5 + Math.floor(tileId / 256);
  }

  var w = this._tileWidth;
  var h = this._tileHeight;
  var sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * w;
  var sy = (Math.floor((tileId % 256) / 8) % 16) * h;

  layer.addRect(setNumber, sx, sy, dx, dy, w, h);
};

/**
 * @method _drawAutotile
 * @param {Array} layers
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
ShaderTilemap.prototype._drawAutotile = function (layer, tileId, dx, dy) {
  var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
  var kind = Tilemap.getAutotileKind(tileId);
  var shape = Tilemap.getAutotileShape(tileId);
  var tx = kind % 8;
  var ty = Math.floor(kind / 8);
  var bx = 0;
  var by = 0;
  var setNumber = 0;
  var isTable = false;
  var animX = 0,
    animY = 0;

  if (Tilemap.isTileA1(tileId)) {
    setNumber = 0;
    if (kind === 0) {
      animX = 2;
      by = 0;
    } else if (kind === 1) {
      animX = 2;
      by = 3;
    } else if (kind === 2) {
      bx = 6;
      by = 0;
    } else if (kind === 3) {
      bx = 6;
      by = 3;
    } else {
      bx = Math.floor(tx / 4) * 8;
      by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;
      if (kind % 2 === 0) {
        animX = 2;
      } else {
        bx += 6;
        autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
        animY = 1;
      }
    }
  } else if (Tilemap.isTileA2(tileId)) {
    setNumber = 1;
    bx = tx * 2;
    by = (ty - 2) * 3;
    isTable = this._isTableTile(tileId);
  } else if (Tilemap.isTileA3(tileId)) {
    setNumber = 2;
    bx = tx * 2;
    by = (ty - 6) * 2;
    autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
  } else if (Tilemap.isTileA4(tileId)) {
    setNumber = 3;
    bx = tx * 2;
    by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
    if (ty % 2 === 1) {
      autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
    }
  }

  var table = autotileTable[shape];
  var w1 = this._tileWidth / 2;
  var h1 = this._tileHeight / 2;
  for (var i = 0; i < 4; i++) {
    var qsx = table[i][0];
    var qsy = table[i][1];
    var sx1 = (bx * 2 + qsx) * w1;
    var sy1 = (by * 2 + qsy) * h1;
    var dx1 = dx + (i % 2) * w1;
    var dy1 = dy + Math.floor(i / 2) * h1;
    if (isTable && (qsy === 1 || qsy === 5)) {
      var qsx2 = qsx;
      var qsy2 = 3;
      if (qsy === 1) {
        //qsx2 = [0, 3, 2, 1][qsx];
        qsx2 = (4 - qsx) % 4;
      }
      var sx2 = (bx * 2 + qsx2) * w1;
      var sy2 = (by * 2 + qsy2) * h1;
      layer.addRect(setNumber, sx2, sy2, dx1, dy1, w1, h1, animX, animY);
      layer.addRect(
        setNumber,
        sx1,
        sy1,
        dx1,
        dy1 + h1 / 2,
        w1,
        h1 / 2,
        animX,
        animY
      );
    } else {
      layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1, animX, animY);
    }
  }
};

/**
 * @method _drawTableEdge
 * @param {Array} layers
 * @param {Number} tileId
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
ShaderTilemap.prototype._drawTableEdge = function (layer, tileId, dx, dy) {
  if (Tilemap.isTileA2(tileId)) {
    var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
    var kind = Tilemap.getAutotileKind(tileId);
    var shape = Tilemap.getAutotileShape(tileId);
    var tx = kind % 8;
    var ty = Math.floor(kind / 8);
    var setNumber = 1;
    var bx = tx * 2;
    var by = (ty - 2) * 3;
    var table = autotileTable[shape];
    var w1 = this._tileWidth / 2;
    var h1 = this._tileHeight / 2;
    for (var i = 0; i < 2; i++) {
      var qsx = table[2 + i][0];
      var qsy = table[2 + i][1];
      var sx1 = (bx * 2 + qsx) * w1;
      var sy1 = (by * 2 + qsy) * h1 + h1 / 2;
      var dx1 = dx + (i % 2) * w1;
      var dy1 = dy + Math.floor(i / 2) * h1;
      layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1 / 2);
    }
  }
};

/**
 * @method _drawShadow
 * @param {Number} shadowBits
 * @param {Number} dx
 * @param {Number} dy
 * @private
 */
ShaderTilemap.prototype._drawShadow = function (layer, shadowBits, dx, dy) {
  if (shadowBits & 0x0f) {
    var w1 = this._tileWidth / 2;
    var h1 = this._tileHeight / 2;
    for (var i = 0; i < 4; i++) {
      if (shadowBits & (1 << i)) {
        var dx1 = dx + (i % 2) * w1;
        var dy1 = dy + Math.floor(i / 2) * h1;
        layer.addRect(-1, 0, 0, dx1, dy1, w1, h1);
      }
    }
  }
};
//-----------------------------------------------------------------------------
/**
 * The sprite object for a tiling image.
 *
 * @class TilingSprite
 * @constructor
 * @param {Bitmap} bitmap The image for the tiling sprite
 */
function TilingSprite() {
  this.initialize.apply(this, arguments);
}

TilingSprite.prototype = Object.create(
  PIXI.extras.PictureTilingSprite.prototype
);
TilingSprite.prototype.constructor = TilingSprite;

TilingSprite.prototype.initialize = function (bitmap) {
  var texture = new PIXI.Texture(new PIXI.BaseTexture());

  PIXI.extras.PictureTilingSprite.call(this, texture);

  this._bitmap = null;
  this._width = 0;
  this._height = 0;
  this._frame = new Rectangle();
  this.spriteId = Sprite._counter++;
  /**
   * The origin point of the tiling sprite for scrolling.
   *
   * @property origin
   * @type Point
   */
  this.origin = new Point();

  this.bitmap = bitmap;
};

TilingSprite.prototype._renderCanvas_PIXI =
  PIXI.extras.PictureTilingSprite.prototype._renderCanvas;
TilingSprite.prototype._renderWebGL_PIXI =
  PIXI.extras.PictureTilingSprite.prototype._renderWebGL;

/**
 * @method _renderCanvas
 * @param {Object} renderer
 * @private
 */
TilingSprite.prototype._renderCanvas = function (renderer) {
  if (this._bitmap) {
    this._bitmap.touch();
  }
  if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
    this._renderCanvas_PIXI(renderer);
  }
};

/**
 * @method _renderWebGL
 * @param {Object} renderer
 * @private
 */
TilingSprite.prototype._renderWebGL = function (renderer) {
  if (this._bitmap) {
    this._bitmap.touch();
  }
  if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
    if (this._bitmap) {
      this._bitmap.checkDirty();
    }
    this._renderWebGL_PIXI(renderer);
  }
};

/**
 * The image for the tiling sprite.
 *
 * @property bitmap
 * @type Bitmap
 */
Object.defineProperty(TilingSprite.prototype, "bitmap", {
  get: function () {
    return this._bitmap;
  },
  set: function (value) {
    if (this._bitmap !== value) {
      this._bitmap = value;
      if (this._bitmap) {
        this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
      } else {
        this.texture.frame = Rectangle.emptyRectangle;
      }
    }
  },
  configurable: true,
});

/**
 * The opacity of the tiling sprite (0 to 255).
 *
 * @property opacity
 * @type Number
 */
Object.defineProperty(TilingSprite.prototype, "opacity", {
  get: function () {
    return this.alpha * 255;
  },
  set: function (value) {
    this.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

/**
 * Updates the tiling sprite for each frame.
 *
 * @method update
 */
TilingSprite.prototype.update = function () {
  this.children.forEach(function (child) {
    if (child.update) {
      child.update();
    }
  });
};

/**
 * Sets the x, y, width, and height all at once.
 *
 * @method move
 * @param {Number} x The x coordinate of the tiling sprite
 * @param {Number} y The y coordinate of the tiling sprite
 * @param {Number} width The width of the tiling sprite
 * @param {Number} height The height of the tiling sprite
 */
TilingSprite.prototype.move = function (x, y, width, height) {
  this.x = x || 0;
  this.y = y || 0;
  this._width = width || 0;
  this._height = height || 0;
};

/**
 * Specifies the region of the image that the tiling sprite will use.
 *
 * @method setFrame
 * @param {Number} x The x coordinate of the frame
 * @param {Number} y The y coordinate of the frame
 * @param {Number} width The width of the frame
 * @param {Number} height The height of the frame
 */
TilingSprite.prototype.setFrame = function (x, y, width, height) {
  this._frame.x = x;
  this._frame.y = y;
  this._frame.width = width;
  this._frame.height = height;
  this._refresh();
};

/**
 * @method updateTransform
 * @private
 */
TilingSprite.prototype.updateTransform = function () {
  this.tilePosition.x = Math.round(-this.origin.x);
  this.tilePosition.y = Math.round(-this.origin.y);
  this.updateTransformTS();
};

TilingSprite.prototype.updateTransformTS =
  PIXI.extras.TilingSprite.prototype.updateTransform;

/**
 * @method _onBitmapLoad
 * @private
 */
TilingSprite.prototype._onBitmapLoad = function () {
  this.texture.baseTexture = this._bitmap.baseTexture;
  this._refresh();
};

/**
 * @method _refresh
 * @private
 */
TilingSprite.prototype._refresh = function () {
  var frame = this._frame.clone();
  if (frame.width === 0 && frame.height === 0 && this._bitmap) {
    frame.width = this._bitmap.width;
    frame.height = this._bitmap.height;
  }
  this.texture.frame = frame;
  this.texture._updateID++;
  this.tilingTexture = null;
};

TilingSprite.prototype._speedUpCustomBlendModes =
  Sprite.prototype._speedUpCustomBlendModes;

/**
 * @method _renderWebGL
 * @param {Object} renderer
 * @private
 */
TilingSprite.prototype._renderWebGL = function (renderer) {
  if (this._bitmap) {
    this._bitmap.touch();
    this._bitmap.checkDirty();
  }

  this._speedUpCustomBlendModes(renderer);

  this._renderWebGL_PIXI(renderer);
};

// The important members from Pixi.js

/**
 * The visibility of the tiling sprite.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the tiling sprite.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the tiling sprite.
 *
 * @property y
 * @type Number
 */

//-----------------------------------------------------------------------------
/**
 * The sprite which covers the entire game screen.
 *
 * @class ScreenSprite
 * @constructor
 */
function ScreenSprite() {
  this.initialize.apply(this, arguments);
}

ScreenSprite.prototype = Object.create(PIXI.Container.prototype);
ScreenSprite.prototype.constructor = ScreenSprite;

ScreenSprite.prototype.initialize = function () {
  PIXI.Container.call(this);

  this._graphics = new PIXI.Graphics();
  this.addChild(this._graphics);
  this.opacity = 0;

  this._red = -1;
  this._green = -1;
  this._blue = -1;
  this._colorText = "";
  this.setBlack();
};

/**
 * The opacity of the sprite (0 to 255).
 *
 * @property opacity
 * @type Number
 */
Object.defineProperty(ScreenSprite.prototype, "opacity", {
  get: function () {
    return this.alpha * 255;
  },
  set: function (value) {
    this.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

ScreenSprite.YEPWarned = false;
ScreenSprite.warnYep = function () {
  if (!ScreenSprite.YEPWarned) {
    console.log(
      "Deprecation warning. Please update YEP_CoreEngine. ScreenSprite is not a sprite, it has graphics inside."
    );
    ScreenSprite.YEPWarned = true;
  }
};

Object.defineProperty(ScreenSprite.prototype, "anchor", {
  get: function () {
    ScreenSprite.warnYep();
    this.scale.x = 1;
    this.scale.y = 1;
    return { x: 0, y: 0 };
  },
  set: function (value) {
    this.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

Object.defineProperty(ScreenSprite.prototype, "blendMode", {
  get: function () {
    return this._graphics.blendMode;
  },
  set: function (value) {
    this._graphics.blendMode = value;
  },
  configurable: true,
});

/**
 * Sets black to the color of the screen sprite.
 *
 * @method setBlack
 */
ScreenSprite.prototype.setBlack = function () {
  this.setColor(0, 0, 0);
};

/**
 * Sets white to the color of the screen sprite.
 *
 * @method setWhite
 */
ScreenSprite.prototype.setWhite = function () {
  this.setColor(255, 255, 255);
};

/**
 * Sets the color of the screen sprite by values.
 *
 * @method setColor
 * @param {Number} r The red value in the range (0, 255)
 * @param {Number} g The green value in the range (0, 255)
 * @param {Number} b The blue value in the range (0, 255)
 */
ScreenSprite.prototype.setColor = function (r, g, b) {
  if (this._red !== r || this._green !== g || this._blue !== b) {
    r = Math.round(r || 0).clamp(0, 255);
    g = Math.round(g || 0).clamp(0, 255);
    b = Math.round(b || 0).clamp(0, 255);
    this._red = r;
    this._green = g;
    this._blue = b;
    this._colorText = Utils.rgbToCssColor(r, g, b);

    var graphics = this._graphics;
    graphics.clear();
    var intColor = (r << 16) | (g << 8) | b;
    graphics.beginFill(intColor, 1);
    //whole screen with zoom. BWAHAHAHAHA
    graphics.drawRect(
      -Graphics.width * 5,
      -Graphics.height * 5,
      Graphics.width * 10,
      Graphics.height * 10
    );
  }
};

//-----------------------------------------------------------------------------
/**
 * The window in the game.
 *
 * @class Window
 * @constructor
 */
function Window() {
  this.initialize.apply(this, arguments);
}

Window.prototype = Object.create(PIXI.Container.prototype);
Window.prototype.constructor = Window;

Window.prototype.initialize = function () {
  PIXI.Container.call(this);

  this._isWindow = true;
  this._windowskin = null;
  this._width = 0;
  this._height = 0;
  this._cursorRect = new Rectangle();
  this._openness = 255;
  this._animationCount = 0;

  this._padding = 18;
  this._margin = 4;
  this._colorTone = [0, 0, 0];

  this._windowSpriteContainer = null;
  this._windowBackSprite = null;
  this._windowCursorSprite = null;
  this._windowFrameSprite = null;
  this._windowContentsSprite = null;
  this._windowArrowSprites = [];
  this._windowPauseSignSprite = null;

  this._createAllParts();

  /**
   * The origin point of the window for scrolling.
   *
   * @property origin
   * @type Point
   */
  this.origin = new Point();

  /**
   * The active state for the window.
   *
   * @property active
   * @type Boolean
   */
  this.active = true;

  /**
   * The visibility of the down scroll arrow.
   *
   * @property downArrowVisible
   * @type Boolean
   */
  this.downArrowVisible = false;

  /**
   * The visibility of the up scroll arrow.
   *
   * @property upArrowVisible
   * @type Boolean
   */
  this.upArrowVisible = false;

  /**
   * The visibility of the pause sign.
   *
   * @property pause
   * @type Boolean
   */
  this.pause = false;
};

/**
 * The image used as a window skin.
 *
 * @property windowskin
 * @type Bitmap
 */
Object.defineProperty(Window.prototype, "windowskin", {
  get: function () {
    return this._windowskin;
  },
  set: function (value) {
    if (this._windowskin !== value) {
      this._windowskin = value;
      this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
    }
  },
  configurable: true,
});

/**
 * The bitmap used for the window contents.
 *
 * @property contents
 * @type Bitmap
 */
Object.defineProperty(Window.prototype, "contents", {
  get: function () {
    return this._windowContentsSprite.bitmap;
  },
  set: function (value) {
    this._windowContentsSprite.bitmap = value;
  },
  configurable: true,
});

/**
 * The width of the window in pixels.
 *
 * @property width
 * @type Number
 */
Object.defineProperty(Window.prototype, "width", {
  get: function () {
    return this._width;
  },
  set: function (value) {
    this._width = value;
    this._refreshAllParts();
  },
  configurable: true,
});

/**
 * The height of the window in pixels.
 *
 * @property height
 * @type Number
 */
Object.defineProperty(Window.prototype, "height", {
  get: function () {
    return this._height;
  },
  set: function (value) {
    this._height = value;
    this._refreshAllParts();
  },
  configurable: true,
});

/**
 * The size of the padding between the frame and contents.
 *
 * @property padding
 * @type Number
 */
Object.defineProperty(Window.prototype, "padding", {
  get: function () {
    return this._padding;
  },
  set: function (value) {
    this._padding = value;
    this._refreshAllParts();
  },
  configurable: true,
});

/**
 * The size of the margin for the window background.
 *
 * @property margin
 * @type Number
 */
Object.defineProperty(Window.prototype, "margin", {
  get: function () {
    return this._margin;
  },
  set: function (value) {
    this._margin = value;
    this._refreshAllParts();
  },
  configurable: true,
});

/**
 * The opacity of the window without contents (0 to 255).
 *
 * @property opacity
 * @type Number
 */
Object.defineProperty(Window.prototype, "opacity", {
  get: function () {
    return this._windowSpriteContainer.alpha * 255;
  },
  set: function (value) {
    this._windowSpriteContainer.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

/**
 * The opacity of the window background (0 to 255).
 *
 * @property backOpacity
 * @type Number
 */
Object.defineProperty(Window.prototype, "backOpacity", {
  get: function () {
    return this._windowBackSprite.alpha * 255;
  },
  set: function (value) {
    this._windowBackSprite.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

/**
 * The opacity of the window contents (0 to 255).
 *
 * @property contentsOpacity
 * @type Number
 */
Object.defineProperty(Window.prototype, "contentsOpacity", {
  get: function () {
    return this._windowContentsSprite.alpha * 255;
  },
  set: function (value) {
    this._windowContentsSprite.alpha = value.clamp(0, 255) / 255;
  },
  configurable: true,
});

/**
 * The openness of the window (0 to 255).
 *
 * @property openness
 * @type Number
 */
Object.defineProperty(Window.prototype, "openness", {
  get: function () {
    return this._openness;
  },
  set: function (value) {
    if (this._openness !== value) {
      this._openness = value.clamp(0, 255);
      this._windowSpriteContainer.scale.y = this._openness / 255;
      this._windowSpriteContainer.y =
        (this.height / 2) * (1 - this._openness / 255);
    }
  },
  configurable: true,
});

/**
 * Updates the window for each frame.
 *
 * @method update
 */
Window.prototype.update = function () {
  if (this.active) {
    this._animationCount++;
  }
  this.children.forEach(function (child) {
    if (child.update) {
      child.update();
    }
  });
};

/**
 * Sets the x, y, width, and height all at once.
 *
 * @method move
 * @param {Number} x The x coordinate of the window
 * @param {Number} y The y coordinate of the window
 * @param {Number} width The width of the window
 * @param {Number} height The height of the window
 */
Window.prototype.move = function (x, y, width, height) {
  this.x = x || 0;
  this.y = y || 0;
  if (this._width !== width || this._height !== height) {
    this._width = width || 0;
    this._height = height || 0;
    this._refreshAllParts();
  }
};

/**
 * Returns true if the window is completely open (openness == 255).
 *
 * @method isOpen
 */
Window.prototype.isOpen = function () {
  return this._openness >= 255;
};

/**
 * Returns true if the window is completely closed (openness == 0).
 *
 * @method isClosed
 */
Window.prototype.isClosed = function () {
  return this._openness <= 0;
};

/**
 * Sets the position of the command cursor.
 *
 * @method setCursorRect
 * @param {Number} x The x coordinate of the cursor
 * @param {Number} y The y coordinate of the cursor
 * @param {Number} width The width of the cursor
 * @param {Number} height The height of the cursor
 */
Window.prototype.setCursorRect = function (x, y, width, height) {
  var cx = Math.floor(x || 0);
  var cy = Math.floor(y || 0);
  var cw = Math.floor(width || 0);
  var ch = Math.floor(height || 0);
  var rect = this._cursorRect;
  if (
    rect.x !== cx ||
    rect.y !== cy ||
    rect.width !== cw ||
    rect.height !== ch
  ) {
    this._cursorRect.x = cx;
    this._cursorRect.y = cy;
    this._cursorRect.width = cw;
    this._cursorRect.height = ch;
    this._refreshCursor();
  }
};

/**
 * Changes the color of the background.
 *
 * @method setTone
 * @param {Number} r The red value in the range (-255, 255)
 * @param {Number} g The green value in the range (-255, 255)
 * @param {Number} b The blue value in the range (-255, 255)
 */
Window.prototype.setTone = function (r, g, b) {
  var tone = this._colorTone;
  if (r !== tone[0] || g !== tone[1] || b !== tone[2]) {
    this._colorTone = [r, g, b];
    this._refreshBack();
  }
};

/**
 * Adds a child between the background and contents.
 *
 * @method addChildToBack
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */
Window.prototype.addChildToBack = function (child) {
  var containerIndex = this.children.indexOf(this._windowSpriteContainer);
  return this.addChildAt(child, containerIndex + 1);
};

/**
 * @method updateTransform
 * @private
 */
Window.prototype.updateTransform = function () {
  this._updateCursor();
  this._updateArrows();
  this._updatePauseSign();
  this._updateContents();
  PIXI.Container.prototype.updateTransform.call(this);
};

/**
 * @method _createAllParts
 * @private
 */
Window.prototype._createAllParts = function () {
  this._windowSpriteContainer = new PIXI.Container();
  this._windowBackSprite = new Sprite();
  this._windowCursorSprite = new Sprite();
  this._windowFrameSprite = new Sprite();
  this._windowContentsSprite = new Sprite();
  this._downArrowSprite = new Sprite();
  this._upArrowSprite = new Sprite();
  this._windowPauseSignSprite = new Sprite();
  this._windowBackSprite.bitmap = new Bitmap(1, 1);
  this._windowBackSprite.alpha = 192 / 255;
  this.addChild(this._windowSpriteContainer);
  this._windowSpriteContainer.addChild(this._windowBackSprite);
  this._windowSpriteContainer.addChild(this._windowFrameSprite);
  this.addChild(this._windowCursorSprite);
  this.addChild(this._windowContentsSprite);
  this.addChild(this._downArrowSprite);
  this.addChild(this._upArrowSprite);
  this.addChild(this._windowPauseSignSprite);
};

/**
 * @method _onWindowskinLoad
 * @private
 */
Window.prototype._onWindowskinLoad = function () {
  this._refreshAllParts();
};

/**
 * @method _refreshAllParts
 * @private
 */
Window.prototype._refreshAllParts = function () {
  this._refreshBack();
  this._refreshFrame();
  this._refreshCursor();
  this._refreshContents();
  this._refreshArrows();
  this._refreshPauseSign();
};

/**
 * @method _refreshBack
 * @private
 */
Window.prototype._refreshBack = function () {
  var m = this._margin;
  var w = this._width - m * 2;
  var h = this._height - m * 2;
  var bitmap = new Bitmap(w, h);

  this._windowBackSprite.bitmap = bitmap;
  this._windowBackSprite.setFrame(0, 0, w, h);
  this._windowBackSprite.move(m, m);

  if (w > 0 && h > 0 && this._windowskin) {
    var p = 96;
    bitmap.blt(this._windowskin, 0, 0, p, p, 0, 0, w, h);
    for (var y = 0; y < h; y += p) {
      for (var x = 0; x < w; x += p) {
        bitmap.blt(this._windowskin, 0, p, p, p, x, y, p, p);
      }
    }
    var tone = this._colorTone;
    bitmap.adjustTone(tone[0], tone[1], tone[2]);
  }
};

/**
 * @method _refreshFrame
 * @private
 */
Window.prototype._refreshFrame = function () {
  var w = this._width;
  var h = this._height;
  var m = 24;
  var bitmap = new Bitmap(w, h);

  this._windowFrameSprite.bitmap = bitmap;
  this._windowFrameSprite.setFrame(0, 0, w, h);

  if (w > 0 && h > 0 && this._windowskin) {
    var skin = this._windowskin;
    var p = 96;
    var q = 96;
    bitmap.blt(skin, p + m, 0 + 0, p - m * 2, m, m, 0, w - m * 2, m);
    bitmap.blt(skin, p + m, 0 + q - m, p - m * 2, m, m, h - m, w - m * 2, m);
    bitmap.blt(skin, p + 0, 0 + m, m, p - m * 2, 0, m, m, h - m * 2);
    bitmap.blt(skin, p + q - m, 0 + m, m, p - m * 2, w - m, m, m, h - m * 2);
    bitmap.blt(skin, p + 0, 0 + 0, m, m, 0, 0, m, m);
    bitmap.blt(skin, p + q - m, 0 + 0, m, m, w - m, 0, m, m);
    bitmap.blt(skin, p + 0, 0 + q - m, m, m, 0, h - m, m, m);
    bitmap.blt(skin, p + q - m, 0 + q - m, m, m, w - m, h - m, m, m);
  }
};

/**
 * @method _refreshCursor
 * @private
 */
Window.prototype._refreshCursor = function () {
  var pad = this._padding;
  var x = this._cursorRect.x + pad - this.origin.x;
  var y = this._cursorRect.y + pad - this.origin.y;
  var w = this._cursorRect.width;
  var h = this._cursorRect.height;
  var m = 4;
  var x2 = Math.max(x, pad);
  var y2 = Math.max(y, pad);
  var ox = x - x2;
  var oy = y - y2;
  var w2 = Math.min(w, this._width - pad - x2);
  var h2 = Math.min(h, this._height - pad - y2);
  var bitmap = new Bitmap(w2, h2);

  this._windowCursorSprite.bitmap = bitmap;
  this._windowCursorSprite.setFrame(0, 0, w2, h2);
  this._windowCursorSprite.move(x2, y2);

  if (w > 0 && h > 0 && this._windowskin) {
    var skin = this._windowskin;
    var p = 96;
    var q = 48;
    bitmap.blt(
      skin,
      p + m,
      p + m,
      q - m * 2,
      q - m * 2,
      ox + m,
      oy + m,
      w - m * 2,
      h - m * 2
    );
    bitmap.blt(skin, p + m, p + 0, q - m * 2, m, ox + m, oy + 0, w - m * 2, m);
    bitmap.blt(
      skin,
      p + m,
      p + q - m,
      q - m * 2,
      m,
      ox + m,
      oy + h - m,
      w - m * 2,
      m
    );
    bitmap.blt(skin, p + 0, p + m, m, q - m * 2, ox + 0, oy + m, m, h - m * 2);
    bitmap.blt(
      skin,
      p + q - m,
      p + m,
      m,
      q - m * 2,
      ox + w - m,
      oy + m,
      m,
      h - m * 2
    );
    bitmap.blt(skin, p + 0, p + 0, m, m, ox + 0, oy + 0, m, m);
    bitmap.blt(skin, p + q - m, p + 0, m, m, ox + w - m, oy + 0, m, m);
    bitmap.blt(skin, p + 0, p + q - m, m, m, ox + 0, oy + h - m, m, m);
    bitmap.blt(skin, p + q - m, p + q - m, m, m, ox + w - m, oy + h - m, m, m);
  }
};

/**
 * @method _refreshContents
 * @private
 */
Window.prototype._refreshContents = function () {
  this._windowContentsSprite.move(this.padding, this.padding);
};

/**
 * @method _refreshArrows
 * @private
 */
Window.prototype._refreshArrows = function () {
  var w = this._width;
  var h = this._height;
  var p = 24;
  var q = p / 2;
  var sx = 96 + p;
  var sy = 0 + p;
  this._downArrowSprite.bitmap = this._windowskin;
  this._downArrowSprite.anchor.x = 0.5;
  this._downArrowSprite.anchor.y = 0.5;
  this._downArrowSprite.setFrame(sx + q, sy + q + p, p, q);
  this._downArrowSprite.move(w / 2, h - q);
  this._upArrowSprite.bitmap = this._windowskin;
  this._upArrowSprite.anchor.x = 0.5;
  this._upArrowSprite.anchor.y = 0.5;
  this._upArrowSprite.setFrame(sx + q, sy, p, q);
  this._upArrowSprite.move(w / 2, q);
};

/**
 * @method _refreshPauseSign
 * @private
 */
Window.prototype._refreshPauseSign = function () {
  var sx = 144;
  var sy = 96;
  var p = 24;
  this._windowPauseSignSprite.bitmap = this._windowskin;
  this._windowPauseSignSprite.anchor.x = 0.5;
  this._windowPauseSignSprite.anchor.y = 1;
  this._windowPauseSignSprite.move(this._width / 2, this._height);
  this._windowPauseSignSprite.setFrame(sx, sy, p, p);
  this._windowPauseSignSprite.alpha = 0;
};

/**
 * @method _updateCursor
 * @private
 */
Window.prototype._updateCursor = function () {
  var blinkCount = this._animationCount % 40;
  var cursorOpacity = this.contentsOpacity;
  if (this.active) {
    if (blinkCount < 20) {
      cursorOpacity -= blinkCount * 8;
    } else {
      cursorOpacity -= (40 - blinkCount) * 8;
    }
  }
  this._windowCursorSprite.alpha = cursorOpacity / 255;
  this._windowCursorSprite.visible = this.isOpen();
};

/**
 * @method _updateContents
 * @private
 */
Window.prototype._updateContents = function () {
  var w = this._width - this._padding * 2;
  var h = this._height - this._padding * 2;
  if (w > 0 && h > 0) {
    this._windowContentsSprite.setFrame(this.origin.x, this.origin.y, w, h);
    this._windowContentsSprite.visible = this.isOpen();
  } else {
    this._windowContentsSprite.visible = false;
  }
};

/**
 * @method _updateArrows
 * @private
 */
Window.prototype._updateArrows = function () {
  this._downArrowSprite.visible = this.isOpen() && this.downArrowVisible;
  this._upArrowSprite.visible = this.isOpen() && this.upArrowVisible;
};

/**
 * @method _updatePauseSign
 * @private
 */
Window.prototype._updatePauseSign = function () {
  var sprite = this._windowPauseSignSprite;
  var x = Math.floor(this._animationCount / 16) % 2;
  var y = Math.floor(this._animationCount / 16 / 2) % 2;
  var sx = 144;
  var sy = 96;
  var p = 24;
  if (!this.pause) {
    sprite.alpha = 0;
  } else if (sprite.alpha < 1) {
    sprite.alpha = Math.min(sprite.alpha + 0.1, 1);
  }
  sprite.setFrame(sx + x * p, sy + y * p, p, p);
  sprite.visible = this.isOpen();
};

// The important members from Pixi.js

/**
 * The visibility of the window.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the window.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the window.
 *
 * @property y
 * @type Number
 */

/**
 * [read-only] The array of children of the window.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the window.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The layer which contains game windows.
 *
 * @class WindowLayer
 * @constructor
 */
function WindowLayer() {
  this.initialize.apply(this, arguments);
}

WindowLayer.prototype = Object.create(PIXI.Container.prototype);
WindowLayer.prototype.constructor = WindowLayer;

WindowLayer.prototype.initialize = function () {
  PIXI.Container.call(this);
  this._width = 0;
  this._height = 0;
  this._tempCanvas = null;
  this._translationMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];

  this._windowMask = new PIXI.Graphics();
  this._windowMask.beginFill(0xffffff, 1);
  this._windowMask.drawRect(0, 0, 0, 0);
  this._windowMask.endFill();
  this._windowRect = this._windowMask.graphicsData[0].shape;

  this._renderSprite = null;
  this.filterArea = new PIXI.Rectangle();
  this.filters = [WindowLayer.voidFilter];

  //temporary fix for memory leak bug
  this.on("removed", this.onRemoveAsAChild);
};

WindowLayer.prototype.onRemoveAsAChild = function () {
  this.removeChildren();
};

WindowLayer.voidFilter = new PIXI.filters.VoidFilter();

/**
 * The width of the window layer in pixels.
 *
 * @property width
 * @type Number
 */
Object.defineProperty(WindowLayer.prototype, "width", {
  get: function () {
    return this._width;
  },
  set: function (value) {
    this._width = value;
  },
  configurable: true,
});

/**
 * The height of the window layer in pixels.
 *
 * @property height
 * @type Number
 */
Object.defineProperty(WindowLayer.prototype, "height", {
  get: function () {
    return this._height;
  },
  set: function (value) {
    this._height = value;
  },
  configurable: true,
});

/**
 * Sets the x, y, width, and height all at once.
 *
 * @method move
 * @param {Number} x The x coordinate of the window layer
 * @param {Number} y The y coordinate of the window layer
 * @param {Number} width The width of the window layer
 * @param {Number} height The height of the window layer
 */
WindowLayer.prototype.move = function (x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
};

/**
 * Updates the window layer for each frame.
 *
 * @method update
 */
WindowLayer.prototype.update = function () {
  this.children.forEach(function (child) {
    if (child.update) {
      child.update();
    }
  });
};

/**
 * @method _renderCanvas
 * @param {Object} renderSession
 * @private
 */
WindowLayer.prototype.renderCanvas = function (renderer) {
  if (!this.visible || !this.renderable) {
    return;
  }

  if (!this._tempCanvas) {
    this._tempCanvas = document.createElement("canvas");
  }

  this._tempCanvas.width = Graphics.width;
  this._tempCanvas.height = Graphics.height;

  var realCanvasContext = renderer.context;
  var context = this._tempCanvas.getContext("2d");

  context.save();
  context.clearRect(0, 0, Graphics.width, Graphics.height);
  context.beginPath();
  context.rect(this.x, this.y, this.width, this.height);
  context.closePath();
  context.clip();

  renderer.context = context;

  for (var i = 0; i < this.children.length; i++) {
    var child = this.children[i];
    if (child._isWindow && child.visible && child.openness > 0) {
      this._canvasClearWindowRect(renderer, child);
      context.save();
      child.renderCanvas(renderer);
      context.restore();
    }
  }

  context.restore();

  renderer.context = realCanvasContext;
  renderer.context.setTransform(1, 0, 0, 1, 0, 0);
  renderer.context.globalCompositeOperation = "source-over";
  renderer.context.globalAlpha = 1;
  renderer.context.drawImage(this._tempCanvas, 0, 0);

  for (var j = 0; j < this.children.length; j++) {
    if (!this.children[j]._isWindow) {
      this.children[j].renderCanvas(renderer);
    }
  }
};

/**
 * @method _canvasClearWindowRect
 * @param {Object} renderSession
 * @param {Window} window
 * @private
 */
WindowLayer.prototype._canvasClearWindowRect = function (
  renderSession,
  window
) {
  var rx = this.x + window.x;
  var ry =
    this.y + window.y + (window.height / 2) * (1 - window._openness / 255);
  var rw = window.width;
  var rh = (window.height * window._openness) / 255;
  renderSession.context.clearRect(rx, ry, rw, rh);
};

/**
 * @method _renderWebGL
 * @param {Object} renderSession
 * @private
 */
WindowLayer.prototype.renderWebGL = function (renderer) {
  if (!this.visible || !this.renderable) {
    return;
  }

  if (this.children.length == 0) {
    return;
  }

  renderer.flush();
  this.filterArea.copy(this);
  renderer.filterManager.pushFilter(this, this.filters);
  renderer.currentRenderer.start();

  var shift = new PIXI.Point();
  var rt = renderer._activeRenderTarget;
  var projectionMatrix = rt.projectionMatrix;
  shift.x = Math.round(((projectionMatrix.tx + 1) / 2) * rt.sourceFrame.width);
  shift.y = Math.round(((projectionMatrix.ty + 1) / 2) * rt.sourceFrame.height);

  for (var i = 0; i < this.children.length; i++) {
    var child = this.children[i];
    if (child._isWindow && child.visible && child.openness > 0) {
      this._maskWindow(child, shift);
      renderer.maskManager.pushScissorMask(this, this._windowMask);
      renderer.clear();
      renderer.maskManager.popScissorMask();
      renderer.currentRenderer.start();
      child.renderWebGL(renderer);
      renderer.currentRenderer.flush();
    }
  }

  renderer.flush();
  renderer.filterManager.popFilter();
  renderer.maskManager.popScissorMask();

  for (var j = 0; j < this.children.length; j++) {
    if (!this.children[j]._isWindow) {
      this.children[j].renderWebGL(renderer);
    }
  }
};

/**
 * @method _maskWindow
 * @param {Window} window
 * @private
 */
WindowLayer.prototype._maskWindow = function (window, shift) {
  this._windowMask._currentBounds = null;
  this._windowMask.boundsDirty = true;
  var rect = this._windowRect;
  rect.x = this.x + shift.x + window.x;
  rect.y =
    this.x +
    shift.y +
    window.y +
    (window.height / 2) * (1 - window._openness / 255);
  rect.width = window.width;
  rect.height = (window.height * window._openness) / 255;
};

// The important members from Pixi.js

/**
 * The x coordinate of the window layer.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the window layer.
 *
 * @property y
 * @type Number
 */

/**
 * [read-only] The array of children of the window layer.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the window layer.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The weather effect which displays rain, storm, or snow.
 *
 * @class Weather
 * @constructor
 */
function Weather() {
  this.initialize.apply(this, arguments);
}

Weather.prototype = Object.create(PIXI.Container.prototype);
Weather.prototype.constructor = Weather;

Weather.prototype.initialize = function () {
  PIXI.Container.call(this);

  this._width = Graphics.width;
  this._height = Graphics.height;
  this._sprites = [];

  this._createBitmaps();
  this._createDimmer();

  /**
   * The type of the weather in ['none', 'rain', 'storm', 'snow'].
   *
   * @property type
   * @type String
   */
  this.type = "none";

  /**
   * The power of the weather in the range (0, 9).
   *
   * @property power
   * @type Number
   */
  this.power = 0;

  /**
   * The origin point of the weather for scrolling.
   *
   * @property origin
   * @type Point
   */
  this.origin = new Point();
};

/**
 * Updates the weather for each frame.
 *
 * @method update
 */
Weather.prototype.update = function () {
  this._updateDimmer();
  this._updateAllSprites();
};

/**
 * @method _createBitmaps
 * @private
 */
Weather.prototype._createBitmaps = function () {
  this._rainBitmap = new Bitmap(1, 60);
  this._rainBitmap.fillAll("white");
  this._stormBitmap = new Bitmap(2, 100);
  this._stormBitmap.fillAll("white");
  this._snowBitmap = new Bitmap(9, 9);
  this._snowBitmap.drawCircle(4, 4, 4, "white");
};

/**
 * @method _createDimmer
 * @private
 */
Weather.prototype._createDimmer = function () {
  this._dimmerSprite = new ScreenSprite();
  this._dimmerSprite.setColor(80, 80, 80);
  this.addChild(this._dimmerSprite);
};

/**
 * @method _updateDimmer
 * @private
 */
Weather.prototype._updateDimmer = function () {
  this._dimmerSprite.opacity = Math.floor(this.power * 6);
};

/**
 * @method _updateAllSprites
 * @private
 */
Weather.prototype._updateAllSprites = function () {
  var maxSprites = Math.floor(this.power * 10);
  while (this._sprites.length < maxSprites) {
    this._addSprite();
  }
  while (this._sprites.length > maxSprites) {
    this._removeSprite();
  }
  this._sprites.forEach(function (sprite) {
    this._updateSprite(sprite);
    sprite.x = sprite.ax - this.origin.x;
    sprite.y = sprite.ay - this.origin.y;
  }, this);
};

/**
 * @method _addSprite
 * @private
 */
Weather.prototype._addSprite = function () {
  var sprite = new Sprite(this.viewport);
  sprite.opacity = 0;
  this._sprites.push(sprite);
  this.addChild(sprite);
};

/**
 * @method _removeSprite
 * @private
 */
Weather.prototype._removeSprite = function () {
  this.removeChild(this._sprites.pop());
};

/**
 * @method _updateSprite
 * @param {Sprite} sprite
 * @private
 */
Weather.prototype._updateSprite = function (sprite) {
  switch (this.type) {
    case "rain":
      this._updateRainSprite(sprite);
      break;
    case "storm":
      this._updateStormSprite(sprite);
      break;
    case "snow":
      this._updateSnowSprite(sprite);
      break;
  }
  if (sprite.opacity < 40) {
    this._rebornSprite(sprite);
  }
};

/**
 * @method _updateRainSprite
 * @param {Sprite} sprite
 * @private
 */
Weather.prototype._updateRainSprite = function (sprite) {
  sprite.bitmap = this._rainBitmap;
  sprite.rotation = Math.PI / 16;
  sprite.ax -= 6 * Math.sin(sprite.rotation);
  sprite.ay += 6 * Math.cos(sprite.rotation);
  sprite.opacity -= 6;
};

/**
 * @method _updateStormSprite
 * @param {Sprite} sprite
 * @private
 */
Weather.prototype._updateStormSprite = function (sprite) {
  sprite.bitmap = this._stormBitmap;
  sprite.rotation = Math.PI / 8;
  sprite.ax -= 8 * Math.sin(sprite.rotation);
  sprite.ay += 8 * Math.cos(sprite.rotation);
  sprite.opacity -= 8;
};

/**
 * @method _updateSnowSprite
 * @param {Sprite} sprite
 * @private
 */
Weather.prototype._updateSnowSprite = function (sprite) {
  sprite.bitmap = this._snowBitmap;
  sprite.rotation = Math.PI / 16;
  sprite.ax -= 3 * Math.sin(sprite.rotation);
  sprite.ay += 3 * Math.cos(sprite.rotation);
  sprite.opacity -= 3;
};

/**
 * @method _rebornSprite
 * @param {Sprite} sprite
 * @private
 */
Weather.prototype._rebornSprite = function (sprite) {
  sprite.ax = Math.randomInt(Graphics.width + 100) - 100 + this.origin.x;
  sprite.ay = Math.randomInt(Graphics.height + 200) - 200 + this.origin.y;
  sprite.opacity = 160 + Math.randomInt(60);
};

//-----------------------------------------------------------------------------
/**
 * The color matrix filter for WebGL.
 *
 * @class ToneFilter
 * @extends PIXI.Filter
 * @constructor
 */
function ToneFilter() {
  PIXI.filters.ColorMatrixFilter.call(this);
}

ToneFilter.prototype = Object.create(PIXI.filters.ColorMatrixFilter.prototype);
ToneFilter.prototype.constructor = ToneFilter;

/**
 * Changes the hue.
 *
 * @method adjustHue
 * @param {Number} value The hue value in the range (-360, 360)
 */
ToneFilter.prototype.adjustHue = function (value) {
  this.hue(value, true);
};

/**
 * Changes the saturation.
 *
 * @method adjustSaturation
 * @param {Number} value The saturation value in the range (-255, 255)
 */
ToneFilter.prototype.adjustSaturation = function (value) {
  value = (value || 0).clamp(-255, 255) / 255;
  this.saturate(value, true);
};

/**
 * Changes the tone.
 *
 * @method adjustTone
 * @param {Number} r The red strength in the range (-255, 255)
 * @param {Number} g The green strength in the range (-255, 255)
 * @param {Number} b The blue strength in the range (-255, 255)
 */
ToneFilter.prototype.adjustTone = function (r, g, b) {
  r = (r || 0).clamp(-255, 255) / 255;
  g = (g || 0).clamp(-255, 255) / 255;
  b = (b || 0).clamp(-255, 255) / 255;

  if (r !== 0 || g !== 0 || b !== 0) {
    var matrix = [1, 0, 0, r, 0, 0, 1, 0, g, 0, 0, 0, 1, b, 0, 0, 0, 0, 1, 0];

    this._loadMatrix(matrix, true);
  }
};

//-----------------------------------------------------------------------------
/**
 * The sprite which changes the screen color in 2D canvas mode.
 *
 * @class ToneSprite
 * @constructor
 */
function ToneSprite() {
  this.initialize.apply(this, arguments);
}

ToneSprite.prototype = Object.create(PIXI.Container.prototype);
ToneSprite.prototype.constructor = ToneSprite;

ToneSprite.prototype.initialize = function () {
  PIXI.Container.call(this);
  this.clear();
};

/**
 * Clears the tone.
 *
 * @method reset
 */
ToneSprite.prototype.clear = function () {
  this._red = 0;
  this._green = 0;
  this._blue = 0;
  this._gray = 0;
};

/**
 * Sets the tone.
 *
 * @method setTone
 * @param {Number} r The red strength in the range (-255, 255)
 * @param {Number} g The green strength in the range (-255, 255)
 * @param {Number} b The blue strength in the range (-255, 255)
 * @param {Number} gray The grayscale level in the range (0, 255)
 */
ToneSprite.prototype.setTone = function (r, g, b, gray) {
  this._red = Math.round(r || 0).clamp(-255, 255);
  this._green = Math.round(g || 0).clamp(-255, 255);
  this._blue = Math.round(b || 0).clamp(-255, 255);
  this._gray = Math.round(gray || 0).clamp(0, 255);
};

/**
 * @method _renderCanvas
 * @param {Object} renderSession
 * @private
 */
ToneSprite.prototype._renderCanvas = function (renderer) {
  if (this.visible) {
    var context = renderer.context;
    var t = this.worldTransform;
    var r = renderer.resolution;
    var width = Graphics.width;
    var height = Graphics.height;
    context.save();
    context.setTransform(t.a, t.b, t.c, t.d, t.tx * r, t.ty * r);
    if (Graphics.canUseSaturationBlend() && this._gray > 0) {
      context.globalCompositeOperation = "saturation";
      context.globalAlpha = this._gray / 255;
      context.fillStyle = "#ffffff";
      context.fillRect(0, 0, width, height);
    }
    context.globalAlpha = 1;
    var r1 = Math.max(0, this._red);
    var g1 = Math.max(0, this._green);
    var b1 = Math.max(0, this._blue);
    if (r1 || g1 || b1) {
      context.globalCompositeOperation = "lighter";
      context.fillStyle = Utils.rgbToCssColor(r1, g1, b1);
      context.fillRect(0, 0, width, height);
    }
    if (Graphics.canUseDifferenceBlend()) {
      var r2 = Math.max(0, -this._red);
      var g2 = Math.max(0, -this._green);
      var b2 = Math.max(0, -this._blue);
      if (r2 || g2 || b2) {
        context.globalCompositeOperation = "difference";
        context.fillStyle = "#ffffff";
        context.fillRect(0, 0, width, height);
        context.globalCompositeOperation = "lighter";
        context.fillStyle = Utils.rgbToCssColor(r2, g2, b2);
        context.fillRect(0, 0, width, height);
        context.globalCompositeOperation = "difference";
        context.fillStyle = "#ffffff";
        context.fillRect(0, 0, width, height);
      }
    }
    context.restore();
  }
};

/**
 * @method _renderWebGL
 * @param {Object} renderSession
 * @private
 */
ToneSprite.prototype._renderWebGL = function (renderer) {
  // Not supported
};

//-----------------------------------------------------------------------------
/**
 * The root object of the display tree.
 *
 * @class Stage
 * @constructor
 */
function Stage() {
  this.initialize.apply(this, arguments);
}

Stage.prototype = Object.create(PIXI.Container.prototype);
Stage.prototype.constructor = Stage;

Stage.prototype.initialize = function () {
  PIXI.Container.call(this);

  // The interactive flag causes a memory leak.
  this.interactive = false;
};

/**
 * [read-only] The array of children of the stage.
 *
 * @property children
 * @type Array
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The audio object of Web Audio API.
 *
 * @class WebAudio
 * @constructor
 * @param {String} url The url of the audio file
 */
function WebAudio() {
  this.initialize.apply(this, arguments);
}

WebAudio._standAlone = (function (top) {
  return !top.ResourceHandler;
})(this);

WebAudio.prototype.initialize = function (url) {
  if (!WebAudio._initialized) {
    WebAudio.initialize();
  }
  this.clear();

  if (!WebAudio._standAlone) {
    this._loader = ResourceHandler.createLoader(
      url,
      this._load.bind(this, url),
      function () {
        this._hasError = true;
      }.bind(this)
    );
  }
  this._load(url);
  this._url = url;
};

WebAudio._masterVolume = 1;
WebAudio._context = null;
WebAudio._masterGainNode = null;
WebAudio._initialized = false;
WebAudio._unlocked = false;

/**
 * Initializes the audio system.
 *
 * @static
 * @method initialize
 * @param {Boolean} noAudio Flag for the no-audio mode
 * @return {Boolean} True if the audio system is available
 */
WebAudio.initialize = function (noAudio) {
  if (!this._initialized) {
    if (!noAudio) {
      this._createContext();
      this._detectCodecs();
      this._createMasterGainNode();
      this._setupEventHandlers();
    }
    this._initialized = true;
  }
  return !!this._context;
};

/**
 * Checks whether the browser can play ogg files.
 *
 * @static
 * @method canPlayOgg
 * @return {Boolean} True if the browser can play ogg files
 */
WebAudio.canPlayOgg = function () {
  if (!this._initialized) {
    this.initialize();
  }
  return !!this._canPlayOgg;
};

/**
 * Checks whether the browser can play m4a files.
 *
 * @static
 * @method canPlayM4a
 * @return {Boolean} True if the browser can play m4a files
 */
WebAudio.canPlayM4a = function () {
  if (!this._initialized) {
    this.initialize();
  }
  return !!this._canPlayM4a;
};

/**
 * Sets the master volume of the all audio.
 *
 * @static
 * @method setMasterVolume
 * @param {Number} value Master volume (min: 0, max: 1)
 */
WebAudio.setMasterVolume = function (value) {
  this._masterVolume = value;
  if (this._masterGainNode) {
    this._masterGainNode.gain.setValueAtTime(
      this._masterVolume,
      this._context.currentTime
    );
  }
};

/**
 * @static
 * @method _createContext
 * @private
 */
WebAudio._createContext = function () {
  try {
    if (typeof AudioContext !== "undefined") {
      this._context = new AudioContext();
    } else if (typeof webkitAudioContext !== "undefined") {
      this._context = new webkitAudioContext();
    }
  } catch (e) {
    this._context = null;
  }
};

/**
 * @static
 * @method _detectCodecs
 * @private
 */
WebAudio._detectCodecs = function () {
  var audio = document.createElement("audio");
  if (audio.canPlayType) {
    this._canPlayOgg = audio.canPlayType("audio/ogg");
    this._canPlayM4a = audio.canPlayType("audio/mp4");
  }
};

/**
 * @static
 * @method _createMasterGainNode
 * @private
 */
WebAudio._createMasterGainNode = function () {
  var context = WebAudio._context;
  if (context) {
    this._masterGainNode = context.createGain();
    this._masterGainNode.gain.setValueAtTime(
      this._masterVolume,
      context.currentTime
    );
    this._masterGainNode.connect(context.destination);
  }
};

/**
 * @static
 * @method _setupEventHandlers
 * @private
 */
WebAudio._setupEventHandlers = function () {
  var resumeHandler = function () {
    var context = WebAudio._context;
    if (
      context &&
      context.state === "suspended" &&
      typeof context.resume === "function"
    ) {
      context.resume().then(function () {
        WebAudio._onTouchStart();
      });
    } else {
      WebAudio._onTouchStart();
    }
  };
  document.addEventListener("keydown", resumeHandler);
  document.addEventListener("mousedown", resumeHandler);
  document.addEventListener("touchend", resumeHandler);
  document.addEventListener("touchstart", this._onTouchStart.bind(this));
  document.addEventListener(
    "visibilitychange",
    this._onVisibilityChange.bind(this)
  );
};

/**
 * @static
 * @method _onTouchStart
 * @private
 */
WebAudio._onTouchStart = function () {
  var context = WebAudio._context;
  if (context && !this._unlocked) {
    // Unlock Web Audio on iOS
    var node = context.createBufferSource();
    node.start(0);
    this._unlocked = true;
  }
};

/**
 * @static
 * @method _onVisibilityChange
 * @private
 */
WebAudio._onVisibilityChange = function () {
  if (document.visibilityState === "hidden") {
    this._onHide();
  } else {
    this._onShow();
  }
};

/**
 * @static
 * @method _onHide
 * @private
 */
WebAudio._onHide = function () {
  if (this._shouldMuteOnHide()) {
    this._fadeOut(1);
  }
};

/**
 * @static
 * @method _onShow
 * @private
 */
WebAudio._onShow = function () {
  if (this._shouldMuteOnHide()) {
    this._fadeIn(0.5);
  }
};

/**
 * @static
 * @method _shouldMuteOnHide
 * @private
 */
WebAudio._shouldMuteOnHide = function () {
  return Utils.isMobileDevice();
};

/**
 * @static
 * @method _fadeIn
 * @param {Number} duration
 * @private
 */
WebAudio._fadeIn = function (duration) {
  if (this._masterGainNode) {
    var gain = this._masterGainNode.gain;
    var currentTime = WebAudio._context.currentTime;
    gain.setValueAtTime(0, currentTime);
    gain.linearRampToValueAtTime(this._masterVolume, currentTime + duration);
  }
};

/**
 * @static
 * @method _fadeOut
 * @param {Number} duration
 * @private
 */
WebAudio._fadeOut = function (duration) {
  if (this._masterGainNode) {
    var gain = this._masterGainNode.gain;
    var currentTime = WebAudio._context.currentTime;
    gain.setValueAtTime(this._masterVolume, currentTime);
    gain.linearRampToValueAtTime(0, currentTime + duration);
  }
};

/**
 * Clears the audio data.
 *
 * @method clear
 */
WebAudio.prototype.clear = function () {
  this.stop();
  this._buffer = null;
  this._sourceNode = null;
  this._gainNode = null;
  this._pannerNode = null;
  this._totalTime = 0;
  this._sampleRate = 0;
  this._loopStart = 0;
  this._loopLength = 0;
  this._startTime = 0;
  this._volume = 1;
  this._pitch = 1;
  this._pan = 0;
  this._endTimer = null;
  this._loadListeners = [];
  this._stopListeners = [];
  this._hasError = false;
  this._autoPlay = false;
};

/**
 * [read-only] The url of the audio file.
 *
 * @property url
 * @type String
 */
Object.defineProperty(WebAudio.prototype, "url", {
  get: function () {
    return this._url;
  },
  configurable: true,
});

/**
 * The volume of the audio.
 *
 * @property volume
 * @type Number
 */
Object.defineProperty(WebAudio.prototype, "volume", {
  get: function () {
    return this._volume;
  },
  set: function (value) {
    this._volume = value;
    if (this._gainNode) {
      this._gainNode.gain.setValueAtTime(
        this._volume,
        WebAudio._context.currentTime
      );
    }
  },
  configurable: true,
});

/**
 * The pitch of the audio.
 *
 * @property pitch
 * @type Number
 */
Object.defineProperty(WebAudio.prototype, "pitch", {
  get: function () {
    return this._pitch;
  },
  set: function (value) {
    if (this._pitch !== value) {
      this._pitch = value;
      if (this.isPlaying()) {
        this.play(this._sourceNode.loop, 0);
      }
    }
  },
  configurable: true,
});

/**
 * The pan of the audio.
 *
 * @property pan
 * @type Number
 */
Object.defineProperty(WebAudio.prototype, "pan", {
  get: function () {
    return this._pan;
  },
  set: function (value) {
    this._pan = value;
    this._updatePanner();
  },
  configurable: true,
});

/**
 * Checks whether the audio data is ready to play.
 *
 * @method isReady
 * @return {Boolean} True if the audio data is ready to play
 */
WebAudio.prototype.isReady = function () {
  return !!this._buffer;
};

/**
 * Checks whether a loading error has occurred.
 *
 * @method isError
 * @return {Boolean} True if a loading error has occurred
 */
WebAudio.prototype.isError = function () {
  return this._hasError;
};

/**
 * Checks whether the audio is playing.
 *
 * @method isPlaying
 * @return {Boolean} True if the audio is playing
 */
WebAudio.prototype.isPlaying = function () {
  return !!this._sourceNode;
};

/**
 * Plays the audio.
 *
 * @method play
 * @param {Boolean} loop Whether the audio data play in a loop
 * @param {Number} offset The start position to play in seconds
 */
WebAudio.prototype.play = function (loop, offset) {
  if (this.isReady()) {
    offset = offset || 0;
    this._startPlaying(loop, offset);
  } else if (WebAudio._context) {
    this._autoPlay = true;
    this.addLoadListener(
      function () {
        if (this._autoPlay) {
          this.play(loop, offset);
        }
      }.bind(this)
    );
  }
};

/**
 * Stops the audio.
 *
 * @method stop
 */
WebAudio.prototype.stop = function () {
  this._autoPlay = false;
  this._removeEndTimer();
  this._removeNodes();
  if (this._stopListeners) {
    while (this._stopListeners.length > 0) {
      var listner = this._stopListeners.shift();
      listner();
    }
  }
};

/**
 * Performs the audio fade-in.
 *
 * @method fadeIn
 * @param {Number} duration Fade-in time in seconds
 */
WebAudio.prototype.fadeIn = function (duration) {
  if (this.isReady()) {
    if (this._gainNode) {
      var gain = this._gainNode.gain;
      var currentTime = WebAudio._context.currentTime;
      gain.setValueAtTime(0, currentTime);
      gain.linearRampToValueAtTime(this._volume, currentTime + duration);
    }
  } else if (this._autoPlay) {
    this.addLoadListener(
      function () {
        this.fadeIn(duration);
      }.bind(this)
    );
  }
};

/**
 * Performs the audio fade-out.
 *
 * @method fadeOut
 * @param {Number} duration Fade-out time in seconds
 */
WebAudio.prototype.fadeOut = function (duration) {
  if (this._gainNode) {
    var gain = this._gainNode.gain;
    var currentTime = WebAudio._context.currentTime;
    gain.setValueAtTime(this._volume, currentTime);
    gain.linearRampToValueAtTime(0, currentTime + duration);
  }
  this._autoPlay = false;
};

/**
 * Gets the seek position of the audio.
 *
 * @method seek
 */
WebAudio.prototype.seek = function () {
  if (WebAudio._context) {
    var pos = (WebAudio._context.currentTime - this._startTime) * this._pitch;
    if (this._loopLength > 0) {
      while (pos >= this._loopStart + this._loopLength) {
        pos -= this._loopLength;
      }
    }
    return pos;
  } else {
    return 0;
  }
};

/**
 * Add a callback function that will be called when the audio data is loaded.
 *
 * @method addLoadListener
 * @param {Function} listner The callback function
 */
WebAudio.prototype.addLoadListener = function (listner) {
  this._loadListeners.push(listner);
};

/**
 * Add a callback function that will be called when the playback is stopped.
 *
 * @method addStopListener
 * @param {Function} listner The callback function
 */
WebAudio.prototype.addStopListener = function (listner) {
  this._stopListeners.push(listner);
};

/**
 * @method _load
 * @param {String} url
 * @private
 */
WebAudio.prototype._load = function (url) {
  if (WebAudio._context) {
    var xhr = new XMLHttpRequest();
    if (Decrypter.hasEncryptedAudio) url = Decrypter.extToEncryptExt(url);
    xhr.open("GET", url);
    xhr.responseType = "arraybuffer";
    xhr.onload = function () {
      if (xhr.status < 400) {
        this._onXhrLoad(xhr);
      }
    }.bind(this);
    xhr.onerror =
      this._loader ||
      function () {
        this._hasError = true;
      }.bind(this);
    xhr.send();
  }
};

/**
 * @method _onXhrLoad
 * @param {XMLHttpRequest} xhr
 * @private
 */
WebAudio.prototype._onXhrLoad = function (xhr) {
  var array = xhr.response;
  if (Decrypter.hasEncryptedAudio) array = Decrypter.decryptArrayBuffer(array);
  this._readLoopComments(new Uint8Array(array));
  WebAudio._context.decodeAudioData(
    array,
    function (buffer) {
      this._buffer = buffer;
      this._totalTime = buffer.duration;
      if (this._loopLength > 0 && this._sampleRate > 0) {
        this._loopStart /= this._sampleRate;
        this._loopLength /= this._sampleRate;
      } else {
        this._loopStart = 0;
        this._loopLength = this._totalTime;
      }
      this._onLoad();
    }.bind(this)
  );
};

/**
 * @method _startPlaying
 * @param {Boolean} loop
 * @param {Number} offset
 * @private
 */
WebAudio.prototype._startPlaying = function (loop, offset) {
  if (this._loopLength > 0) {
    while (offset >= this._loopStart + this._loopLength) {
      offset -= this._loopLength;
    }
  }
  this._removeEndTimer();
  this._removeNodes();
  this._createNodes();
  this._connectNodes();
  this._sourceNode.loop = loop;
  this._sourceNode.start(0, offset);
  this._startTime = WebAudio._context.currentTime - offset / this._pitch;
  this._createEndTimer();
};

/**
 * @method _createNodes
 * @private
 */
WebAudio.prototype._createNodes = function () {
  var context = WebAudio._context;
  this._sourceNode = context.createBufferSource();
  this._sourceNode.buffer = this._buffer;
  this._sourceNode.loopStart = this._loopStart;
  this._sourceNode.loopEnd = this._loopStart + this._loopLength;
  this._sourceNode.playbackRate.setValueAtTime(
    this._pitch,
    context.currentTime
  );
  this._gainNode = context.createGain();
  this._gainNode.gain.setValueAtTime(this._volume, context.currentTime);
  this._pannerNode = context.createPanner();
  this._pannerNode.panningModel = "equalpower";
  this._updatePanner();
};

/**
 * @method _connectNodes
 * @private
 */
WebAudio.prototype._connectNodes = function () {
  this._sourceNode.connect(this._gainNode);
  this._gainNode.connect(this._pannerNode);
  this._pannerNode.connect(WebAudio._masterGainNode);
};

/**
 * @method _removeNodes
 * @private
 */
WebAudio.prototype._removeNodes = function () {
  if (this._sourceNode) {
    this._sourceNode.stop(0);
    this._sourceNode = null;
    this._gainNode = null;
    this._pannerNode = null;
  }
};

/**
 * @method _createEndTimer
 * @private
 */
WebAudio.prototype._createEndTimer = function () {
  if (this._sourceNode && !this._sourceNode.loop) {
    var endTime = this._startTime + this._totalTime / this._pitch;
    var delay = endTime - WebAudio._context.currentTime;
    this._endTimer = setTimeout(
      function () {
        this.stop();
      }.bind(this),
      delay * 1000
    );
  }
};

/**
 * @method _removeEndTimer
 * @private
 */
WebAudio.prototype._removeEndTimer = function () {
  if (this._endTimer) {
    clearTimeout(this._endTimer);
    this._endTimer = null;
  }
};

/**
 * @method _updatePanner
 * @private
 */
WebAudio.prototype._updatePanner = function () {
  if (this._pannerNode) {
    var x = this._pan;
    var z = 1 - Math.abs(x);
    this._pannerNode.setPosition(x, 0, z);
  }
};

/**
 * @method _onLoad
 * @private
 */
WebAudio.prototype._onLoad = function () {
  while (this._loadListeners.length > 0) {
    var listner = this._loadListeners.shift();
    listner();
  }
};

/**
 * @method _readLoopComments
 * @param {Uint8Array} array
 * @private
 */
WebAudio.prototype._readLoopComments = function (array) {
  this._readOgg(array);
  this._readMp4(array);
};

/**
 * @method _readOgg
 * @param {Uint8Array} array
 * @private
 */
WebAudio.prototype._readOgg = function (array) {
  var index = 0;
  while (index < array.length) {
    if (this._readFourCharacters(array, index) === "OggS") {
      index += 26;
      var vorbisHeaderFound = false;
      var numSegments = array[index++];
      var segments = [];
      for (var i = 0; i < numSegments; i++) {
        segments.push(array[index++]);
      }
      for (i = 0; i < numSegments; i++) {
        if (this._readFourCharacters(array, index + 1) === "vorb") {
          var headerType = array[index];
          if (headerType === 1) {
            this._sampleRate = this._readLittleEndian(array, index + 12);
          } else if (headerType === 3) {
            this._readMetaData(array, index, segments[i]);
          }
          vorbisHeaderFound = true;
        }
        index += segments[i];
      }
      if (!vorbisHeaderFound) {
        break;
      }
    } else {
      break;
    }
  }
};

/**
 * @method _readMp4
 * @param {Uint8Array} array
 * @private
 */
WebAudio.prototype._readMp4 = function (array) {
  if (this._readFourCharacters(array, 4) === "ftyp") {
    var index = 0;
    while (index < array.length) {
      var size = this._readBigEndian(array, index);
      var name = this._readFourCharacters(array, index + 4);
      if (name === "moov") {
        index += 8;
      } else {
        if (name === "mvhd") {
          this._sampleRate = this._readBigEndian(array, index + 20);
        }
        if (name === "udta" || name === "meta") {
          this._readMetaData(array, index, size);
        }
        index += size;
        if (size <= 1) {
          break;
        }
      }
    }
  }
};

/**
 * @method _readMetaData
 * @param {Uint8Array} array
 * @param {Number} index
 * @param {Number} size
 * @private
 */
WebAudio.prototype._readMetaData = function (array, index, size) {
  for (var i = index; i < index + size - 10; i++) {
    if (this._readFourCharacters(array, i) === "LOOP") {
      var text = "";
      while (array[i] > 0) {
        text += String.fromCharCode(array[i++]);
      }
      if (text.match(/LOOPSTART=([0-9]+)/)) {
        this._loopStart = parseInt(RegExp.$1);
      }
      if (text.match(/LOOPLENGTH=([0-9]+)/)) {
        this._loopLength = parseInt(RegExp.$1);
      }
      if (text == "LOOPSTART" || text == "LOOPLENGTH") {
        var text2 = "";
        i += 16;
        while (array[i] > 0) {
          text2 += String.fromCharCode(array[i++]);
        }
        if (text == "LOOPSTART") {
          this._loopStart = parseInt(text2);
        } else {
          this._loopLength = parseInt(text2);
        }
      }
    }
  }
};

/**
 * @method _readLittleEndian
 * @param {Uint8Array} array
 * @param {Number} index
 * @private
 */
WebAudio.prototype._readLittleEndian = function (array, index) {
  return (
    array[index + 3] * 0x1000000 +
    array[index + 2] * 0x10000 +
    array[index + 1] * 0x100 +
    array[index + 0]
  );
};

/**
 * @method _readBigEndian
 * @param {Uint8Array} array
 * @param {Number} index
 * @private
 */
WebAudio.prototype._readBigEndian = function (array, index) {
  return (
    array[index + 0] * 0x1000000 +
    array[index + 1] * 0x10000 +
    array[index + 2] * 0x100 +
    array[index + 3]
  );
};

/**
 * @method _readFourCharacters
 * @param {Uint8Array} array
 * @param {Number} index
 * @private
 */
WebAudio.prototype._readFourCharacters = function (array, index) {
  var string = "";
  for (var i = 0; i < 4; i++) {
    string += String.fromCharCode(array[index + i]);
  }
  return string;
};

//-----------------------------------------------------------------------------
/**
 * The static class that handles HTML5 Audio.
 *
 * @class Html5Audio
 * @constructor
 */
function Html5Audio() {
  throw new Error("This is a static class");
}

Html5Audio._initialized = false;
Html5Audio._unlocked = false;
Html5Audio._audioElement = null;
Html5Audio._gainTweenInterval = null;
Html5Audio._tweenGain = 0;
Html5Audio._tweenTargetGain = 0;
Html5Audio._tweenGainStep = 0;
Html5Audio._staticSePath = null;

/**
 * Sets up the Html5 Audio.
 *
 * @static
 * @method setup
 * @param {String} url The url of the audio file
 */
Html5Audio.setup = function (url) {
  if (!this._initialized) {
    this.initialize();
  }
  this.clear();

  if (Decrypter.hasEncryptedAudio && this._audioElement.src) {
    window.URL.revokeObjectURL(this._audioElement.src);
  }
  this._url = url;
};

/**
 * Initializes the audio system.
 *
 * @static
 * @method initialize
 * @return {Boolean} True if the audio system is available
 */
Html5Audio.initialize = function () {
  if (!this._initialized) {
    if (!this._audioElement) {
      try {
        this._audioElement = new Audio();
      } catch (e) {
        this._audioElement = null;
      }
    }
    if (!!this._audioElement) this._setupEventHandlers();
    this._initialized = true;
  }
  return !!this._audioElement;
};

/**
 * @static
 * @method _setupEventHandlers
 * @private
 */
Html5Audio._setupEventHandlers = function () {
  document.addEventListener("touchstart", this._onTouchStart.bind(this));
  document.addEventListener(
    "visibilitychange",
    this._onVisibilityChange.bind(this)
  );
  this._audioElement.addEventListener(
    "loadeddata",
    this._onLoadedData.bind(this)
  );
  this._audioElement.addEventListener("error", this._onError.bind(this));
  this._audioElement.addEventListener("ended", this._onEnded.bind(this));
};

/**
 * @static
 * @method _onTouchStart
 * @private
 */
Html5Audio._onTouchStart = function () {
  if (this._audioElement && !this._unlocked) {
    if (this._isLoading) {
      this._load(this._url);
      this._unlocked = true;
    } else {
      if (this._staticSePath) {
        this._audioElement.src = this._staticSePath;
        this._audioElement.volume = 0;
        this._audioElement.loop = false;
        this._audioElement.play();
        this._unlocked = true;
      }
    }
  }
};

/**
 * @static
 * @method _onVisibilityChange
 * @private
 */
Html5Audio._onVisibilityChange = function () {
  if (document.visibilityState === "hidden") {
    this._onHide();
  } else {
    this._onShow();
  }
};

/**
 * @static
 * @method _onLoadedData
 * @private
 */
Html5Audio._onLoadedData = function () {
  this._buffered = true;
  if (this._unlocked) this._onLoad();
};

/**
 * @static
 * @method _onError
 * @private
 */
Html5Audio._onError = function () {
  this._hasError = true;
};

/**
 * @static
 * @method _onEnded
 * @private
 */
Html5Audio._onEnded = function () {
  if (!this._audioElement.loop) {
    this.stop();
  }
};

/**
 * @static
 * @method _onHide
 * @private
 */
Html5Audio._onHide = function () {
  this._audioElement.volume = 0;
  this._tweenGain = 0;
};

/**
 * @static
 * @method _onShow
 * @private
 */
Html5Audio._onShow = function () {
  this.fadeIn(0.5);
};

/**
 * Clears the audio data.
 *
 * @static
 * @method clear
 */
Html5Audio.clear = function () {
  this.stop();
  this._volume = 1;
  this._loadListeners = [];
  this._hasError = false;
  this._autoPlay = false;
  this._isLoading = false;
  this._buffered = false;
};

/**
 * Set the URL of static se.
 *
 * @static
 * @param {String} url
 */
Html5Audio.setStaticSe = function (url) {
  if (!this._initialized) {
    this.initialize();
    this.clear();
  }
  this._staticSePath = url;
};

/**
 * [read-only] The url of the audio file.
 *
 * @property url
 * @type String
 */
Object.defineProperty(Html5Audio, "url", {
  get: function () {
    return Html5Audio._url;
  },
  configurable: true,
});

/**
 * The volume of the audio.
 *
 * @property volume
 * @type Number
 */
Object.defineProperty(Html5Audio, "volume", {
  get: function () {
    return Html5Audio._volume;
  }.bind(this),
  set: function (value) {
    Html5Audio._volume = value;
    if (Html5Audio._audioElement) {
      Html5Audio._audioElement.volume = this._volume;
    }
  },
  configurable: true,
});

/**
 * Checks whether the audio data is ready to play.
 *
 * @static
 * @method isReady
 * @return {Boolean} True if the audio data is ready to play
 */
Html5Audio.isReady = function () {
  return this._buffered;
};

/**
 * Checks whether a loading error has occurred.
 *
 * @static
 * @method isError
 * @return {Boolean} True if a loading error has occurred
 */
Html5Audio.isError = function () {
  return this._hasError;
};

/**
 * Checks whether the audio is playing.
 *
 * @static
 * @method isPlaying
 * @return {Boolean} True if the audio is playing
 */
Html5Audio.isPlaying = function () {
  return !this._audioElement.paused;
};

/**
 * Plays the audio.
 *
 * @static
 * @method play
 * @param {Boolean} loop Whether the audio data play in a loop
 * @param {Number} offset The start position to play in seconds
 */
Html5Audio.play = function (loop, offset) {
  if (this.isReady()) {
    offset = offset || 0;
    this._startPlaying(loop, offset);
  } else if (Html5Audio._audioElement) {
    this._autoPlay = true;
    this.addLoadListener(
      function () {
        if (this._autoPlay) {
          this.play(loop, offset);
          if (this._gainTweenInterval) {
            clearInterval(this._gainTweenInterval);
            this._gainTweenInterval = null;
          }
        }
      }.bind(this)
    );
    if (!this._isLoading) this._load(this._url);
  }
};

/**
 * Stops the audio.
 *
 * @static
 * @method stop
 */
Html5Audio.stop = function () {
  if (this._audioElement) this._audioElement.pause();
  this._autoPlay = false;
  if (this._tweenInterval) {
    clearInterval(this._tweenInterval);
    this._tweenInterval = null;
    this._audioElement.volume = 0;
  }
};

/**
 * Performs the audio fade-in.
 *
 * @static
 * @method fadeIn
 * @param {Number} duration Fade-in time in seconds
 */
Html5Audio.fadeIn = function (duration) {
  if (this.isReady()) {
    if (this._audioElement) {
      this._tweenTargetGain = this._volume;
      this._tweenGain = 0;
      this._startGainTween(duration);
    }
  } else if (this._autoPlay) {
    this.addLoadListener(
      function () {
        this.fadeIn(duration);
      }.bind(this)
    );
  }
};

/**
 * Performs the audio fade-out.
 *
 * @static
 * @method fadeOut
 * @param {Number} duration Fade-out time in seconds
 */
Html5Audio.fadeOut = function (duration) {
  if (this._audioElement) {
    this._tweenTargetGain = 0;
    this._tweenGain = this._volume;
    this._startGainTween(duration);
  }
};

/**
 * Gets the seek position of the audio.
 *
 * @static
 * @method seek
 */
Html5Audio.seek = function () {
  if (this._audioElement) {
    return this._audioElement.currentTime;
  } else {
    return 0;
  }
};

/**
 * Add a callback function that will be called when the audio data is loaded.
 *
 * @static
 * @method addLoadListener
 * @param {Function} listner The callback function
 */
Html5Audio.addLoadListener = function (listner) {
  this._loadListeners.push(listner);
};

/**
 * @static
 * @method _load
 * @param {String} url
 * @private
 */
Html5Audio._load = function (url) {
  if (this._audioElement) {
    this._isLoading = true;
    this._audioElement.src = url;
    this._audioElement.load();
  }
};

/**
 * @static
 * @method _startPlaying
 * @param {Boolean} loop
 * @param {Number} offset
 * @private
 */
Html5Audio._startPlaying = function (loop, offset) {
  this._audioElement.loop = loop;
  if (this._gainTweenInterval) {
    clearInterval(this._gainTweenInterval);
    this._gainTweenInterval = null;
  }
  if (this._audioElement) {
    this._audioElement.volume = this._volume;
    this._audioElement.currentTime = offset;
    this._audioElement.play();
  }
};

/**
 * @static
 * @method _onLoad
 * @private
 */
Html5Audio._onLoad = function () {
  this._isLoading = false;
  while (this._loadListeners.length > 0) {
    var listener = this._loadListeners.shift();
    listener();
  }
};

/**
 * @static
 * @method _startGainTween
 * @params {Number} duration
 * @private
 */
Html5Audio._startGainTween = function (duration) {
  this._audioElement.volume = this._tweenGain;
  if (this._gainTweenInterval) {
    clearInterval(this._gainTweenInterval);
    this._gainTweenInterval = null;
  }
  this._tweenGainStep =
    (this._tweenTargetGain - this._tweenGain) / (60 * duration);
  this._gainTweenInterval = setInterval(function () {
    Html5Audio._applyTweenValue(Html5Audio._tweenTargetGain);
  }, 1000 / 60);
};

/**
 * @static
 * @method _applyTweenValue
 * @param {Number} volume
 * @private
 */
Html5Audio._applyTweenValue = function (volume) {
  Html5Audio._tweenGain += Html5Audio._tweenGainStep;
  if (Html5Audio._tweenGain < 0 && Html5Audio._tweenGainStep < 0) {
    Html5Audio._tweenGain = 0;
  } else if (Html5Audio._tweenGain > volume && Html5Audio._tweenGainStep > 0) {
    Html5Audio._tweenGain = volume;
  }

  if (Math.abs(Html5Audio._tweenTargetGain - Html5Audio._tweenGain) < 0.01) {
    Html5Audio._tweenGain = Html5Audio._tweenTargetGain;
    clearInterval(Html5Audio._gainTweenInterval);
    Html5Audio._gainTweenInterval = null;
  }

  Html5Audio._audioElement.volume = Html5Audio._tweenGain;
};

//-----------------------------------------------------------------------------
/**
 * The static class that handles JSON with object information.
 *
 * @class JsonEx
 */
function JsonEx() {
  throw new Error("This is a static class");
}

/**
 * The maximum depth of objects.
 *
 * @static
 * @property maxDepth
 * @type Number
 * @default 100
 */
JsonEx.maxDepth = 100;

JsonEx._id = 1;
JsonEx._generateId = function () {
  return JsonEx._id++;
};

/**
 * Converts an object to a JSON string with object information.
 *
 * @static
 * @method stringify
 * @param {Object} object The object to be converted
 * @return {String} The JSON string
 */
JsonEx.stringify = function (object) {
  var circular = [];
  JsonEx._id = 1;
  var json = JSON.stringify(this._encode(object, circular, 0));
  this._cleanMetadata(object);
  this._restoreCircularReference(circular);

  return json;
};

JsonEx._restoreCircularReference = function (circulars) {
  circulars.forEach(function (circular) {
    var key = circular[0];
    var value = circular[1];
    var content = circular[2];

    value[key] = content;
  });
};

/**
 * Parses a JSON string and reconstructs the corresponding object.
 *
 * @static
 * @method parse
 * @param {String} json The JSON string
 * @return {Object} The reconstructed object
 */
JsonEx.parse = function (json) {
  var circular = [];
  var registry = {};
  var contents = this._decode(JSON.parse(json), circular, registry);
  this._cleanMetadata(contents);
  this._linkCircularReference(contents, circular, registry);

  return contents;
};

JsonEx._linkCircularReference = function (contents, circulars, registry) {
  circulars.forEach(function (circular) {
    var key = circular[0];
    var value = circular[1];
    var id = circular[2];

    value[key] = registry[id];
  });
};

JsonEx._cleanMetadata = function (object) {
  if (!object) return;

  delete object["@"];
  delete object["@c"];

  if (typeof object === "object") {
    Object.keys(object).forEach(function (key) {
      var value = object[key];
      if (typeof value === "object") {
        JsonEx._cleanMetadata(value);
      }
    });
  }
};

/**
 * Makes a deep copy of the specified object.
 *
 * @static
 * @method makeDeepCopy
 * @param {Object} object The object to be copied
 * @return {Object} The copied object
 */
JsonEx.makeDeepCopy = function (object) {
  return this.parse(this.stringify(object));
};

/**
 * @static
 * @method _encode
 * @param {Object} value
 * @param {Array} circular
 * @param {Number} depth
 * @return {Object}
 * @private
 */
JsonEx._encode = function (value, circular, depth) {
  depth = depth || 0;
  if (++depth >= this.maxDepth) {
    throw new Error("Object too deep");
  }
  var type = Object.prototype.toString.call(value);
  if (type === "[object Object]" || type === "[object Array]") {
    value["@c"] = JsonEx._generateId();

    var constructorName = this._getConstructorName(value);
    if (constructorName !== "Object" && constructorName !== "Array") {
      value["@"] = constructorName;
    }
    for (var key in value) {
      if (value.hasOwnProperty(key) && !key.match(/^@./)) {
        if (value[key] && typeof value[key] === "object") {
          if (value[key]["@c"]) {
            circular.push([key, value, value[key]]);
            value[key] = { "@r": value[key]["@c"] };
          } else {
            value[key] = this._encode(value[key], circular, depth + 1);

            if (value[key] instanceof Array) {
              //wrap array
              circular.push([key, value, value[key]]);

              value[key] = {
                "@c": value[key]["@c"],
                "@a": value[key],
              };
            }
          }
        } else {
          value[key] = this._encode(value[key], circular, depth + 1);
        }
      }
    }
  }
  depth--;
  return value;
};

/**
 * @static
 * @method _decode
 * @param {Object} value
 * @param {Array} circular
 * @param {Object} registry
 * @return {Object}
 * @private
 */
JsonEx._decode = function (value, circular, registry) {
  var type = Object.prototype.toString.call(value);
  if (type === "[object Object]" || type === "[object Array]") {
    registry[value["@c"]] = value;

    if (value["@"]) {
      var constructor = window[value["@"]];
      if (constructor) {
        value = this._resetPrototype(value, constructor.prototype);
      }
    }
    for (var key in value) {
      if (value.hasOwnProperty(key)) {
        if (value[key] && value[key]["@a"]) {
          //object is array wrapper
          var body = value[key]["@a"];
          body["@c"] = value[key]["@c"];
          value[key] = body;
        }
        if (value[key] && value[key]["@r"]) {
          //object is reference
          circular.push([key, value, value[key]["@r"]]);
        }
        value[key] = this._decode(value[key], circular, registry);
      }
    }
  }
  return value;
};

/**
 * @static
 * @method _getConstructorName
 * @param {Object} value
 * @return {String}
 * @private
 */
JsonEx._getConstructorName = function (value) {
  var name = value.constructor.name;
  if (name === undefined) {
    var func = /^\s*function\s*([A-Za-z0-9_$]*)/;
    name = func.exec(value.constructor)[1];
  }
  return name;
};

/**
 * @static
 * @method _resetPrototype
 * @param {Object} value
 * @param {Object} prototype
 * @return {Object}
 * @private
 */
JsonEx._resetPrototype = function (value, prototype) {
  if (Object.setPrototypeOf !== undefined) {
    Object.setPrototypeOf(value, prototype);
  } else if ("__proto__" in value) {
    value.__proto__ = prototype;
  } else {
    var newValue = Object.create(prototype);
    for (var key in value) {
      if (value.hasOwnProperty(key)) {
        newValue[key] = value[key];
      }
    }
    value = newValue;
  }
  return value;
};

function Decrypter() {
  throw new Error("This is a static class");
}

Decrypter.hasEncryptedImages = false;
Decrypter.hasEncryptedAudio = false;
Decrypter._requestImgFile = [];
Decrypter._headerlength = 16;
Decrypter._xhrOk = 400;
Decrypter._encryptionKey = "";
Decrypter._ignoreList = ["img/system/Window.png"];
Decrypter.SIGNATURE = "5250474d56000000";
Decrypter.VER = "000301";
Decrypter.REMAIN = "0000000000";

Decrypter.checkImgIgnore = function (url) {
  for (var cnt = 0; cnt < this._ignoreList.length; cnt++) {
    if (url === this._ignoreList[cnt]) return true;
  }
  return false;
};

Decrypter.decryptImg = function (url, bitmap) {
  url = this.extToEncryptExt(url);

  var requestFile = new XMLHttpRequest();
  requestFile.open("GET", url);
  requestFile.responseType = "arraybuffer";
  requestFile.send();

  requestFile.onload = function () {
    if (this.status < Decrypter._xhrOk) {
      var arrayBuffer = Decrypter.decryptArrayBuffer(requestFile.response);
      bitmap._image.src = Decrypter.createBlobUrl(arrayBuffer);
      bitmap._image.addEventListener(
        "load",
        (bitmap._loadListener = Bitmap.prototype._onLoad.bind(bitmap))
      );
      bitmap._image.addEventListener(
        "error",
        (bitmap._errorListener =
          bitmap._loader || Bitmap.prototype._onError.bind(bitmap))
      );
    }
  };

  requestFile.onerror = function () {
    if (bitmap._loader) {
      bitmap._loader();
    } else {
      bitmap._onError();
    }
  };
};

Decrypter.decryptHTML5Audio = function (url, bgm, pos) {
  var requestFile = new XMLHttpRequest();
  requestFile.open("GET", url);
  requestFile.responseType = "arraybuffer";
  requestFile.send();

  requestFile.onload = function () {
    if (this.status < Decrypter._xhrOk) {
      var arrayBuffer = Decrypter.decryptArrayBuffer(requestFile.response);
      var url = Decrypter.createBlobUrl(arrayBuffer);
      AudioManager.createDecryptBuffer(url, bgm, pos);
    }
  };
};

Decrypter.cutArrayHeader = function (arrayBuffer, length) {
  return arrayBuffer.slice(length);
};

Decrypter.decryptArrayBuffer = function (arrayBuffer) {
  if (!arrayBuffer) return null;
  var header = new Uint8Array(arrayBuffer, 0, this._headerlength);

  var i;
  var ref = this.SIGNATURE + this.VER + this.REMAIN;
  var refBytes = new Uint8Array(16);
  for (i = 0; i < this._headerlength; i++) {
    refBytes[i] = parseInt("0x" + ref.substr(i * 2, 2), 16);
  }
  for (i = 0; i < this._headerlength; i++) {
    if (header[i] !== refBytes[i]) {
      throw new Error("Header is wrong");
    }
  }

  arrayBuffer = this.cutArrayHeader(arrayBuffer, Decrypter._headerlength);
  var view = new DataView(arrayBuffer);
  this.readEncryptionkey();
  if (arrayBuffer) {
    var byteArray = new Uint8Array(arrayBuffer);
    for (i = 0; i < this._headerlength; i++) {
      byteArray[i] = byteArray[i] ^ parseInt(Decrypter._encryptionKey[i], 16);
      view.setUint8(i, byteArray[i]);
    }
  }

  return arrayBuffer;
};

Decrypter.createBlobUrl = function (arrayBuffer) {
  var blob = new Blob([arrayBuffer]);
  return window.URL.createObjectURL(blob);
};

Decrypter.extToEncryptExt = function (url) {
  var ext = url.split(".").pop();
  var encryptedExt = ext;

  if (ext === "ogg") encryptedExt = ".rpgmvo";
  else if (ext === "m4a") encryptedExt = ".rpgmvm";
  else if (ext === "png") encryptedExt = ".rpgmvp";
  else encryptedExt = ext;

  return url.slice(0, url.lastIndexOf(ext) - 1) + encryptedExt;
};

Decrypter.readEncryptionkey = function () {
  this._encryptionKey = $dataSystem.encryptionKey
    .split(/(.{2})/)
    .filter(Boolean);
};

//-----------------------------------------------------------------------------
/**
 * The static class that handles resource loading.
 *
 * @class ResourceHandler
 */
function ResourceHandler() {
  throw new Error("This is a static class");
}

ResourceHandler._reloaders = [];
ResourceHandler._defaultRetryInterval = [500, 1000, 3000];

ResourceHandler.createLoader = function (
  url,
  retryMethod,
  resignMethod,
  retryInterval
) {
  retryInterval = retryInterval || this._defaultRetryInterval;
  var reloaders = this._reloaders;
  var retryCount = 0;
  return function () {
    if (retryCount < retryInterval.length) {
      setTimeout(retryMethod, retryInterval[retryCount]);
      retryCount++;
    } else {
      if (resignMethod) {
        resignMethod();
      }
      if (url) {
        if (reloaders.length === 0) {
          Graphics.printLoadingError(url);
          SceneManager.stop();
        }
        reloaders.push(function () {
          retryCount = 0;
          retryMethod();
        });
      }
    }
  };
};

ResourceHandler.exists = function () {
  return this._reloaders.length > 0;
};

ResourceHandler.retry = function () {
  if (this._reloaders.length > 0) {
    Graphics.eraseLoadingError();
    SceneManager.resume();
    this._reloaders.forEach(function (reloader) {
      reloader();
    });
    this._reloaders.length = 0;
  }
};