//============================================================================= // rpg_core.js v1.5.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.5.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) { return location.search.slice(1).split('&').contains(name); }; /** * 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 = !Utils.isMobileDevice(); 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; 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 ('' + name + '
' + '' + message + '
'); }; /** * @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('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= 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() { document.addEventListener("touchend", 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('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) { 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; } };