· 5 years ago · Aug 05, 2020, 11:34 AM
1/*============================================================================
2 * ## Plugin Info
3 *----------------------------------------------------------------------------
4 * # Plugin Name
5 * DoubleX RMMZ ES6 Codebase
6 *----------------------------------------------------------------------------
7 * # Introduction
8 * 1. As the majority of the default RMMZ codebase's still written in ES5,
9 * which can cause problems to those avoiding direct prototyping like a
10 * plague or those not being familiar with ES5, this plugin aims to
11 * rewrite the whole codebase into the ES6 standard
12 * 2. This plugin's supposed to be fully compatible with those not written
13 * with this plugin in mind, so plugin developers don't have to write 2
14 * versions per plugin
15 * 3. THIS PLUGIN'S INTENDED TO GIVES AN EXTRA OPTION TO PLUGIN DEVELOPERS
16 * RATHER THAN REPLACING THE DEFAULT RMMZ CODEBASE
17 *----------------------------------------------------------------------------
18 * # Terms Of Use
19 * 1. Commercial use's always allowed and crediting me's always optional.
20 * 2. You shall keep this plugin's Plugin Info part's contents intact.
21 * 3. You shalln't claim that this plugin's written by anyone other than
22 * DoubleX or my aliases. I always reserve the right to deny you from
23 * using any of my plugins anymore if you've violated this.
24 * 4. If you repost this plugin directly(rather than just linking back),
25 * you shall inform me of these direct repostings. I always reserve
26 * the right to request you to edit those direct repostings.
27 * 5. CC BY 4.0, except those conflicting with any of the above, applies
28 * to this plugin, unless you've my permissions not needing follow so.
29 * 6. I always reserve the right to deny you from using this plugin
30 * anymore if you've violated any of the above.
31 *----------------------------------------------------------------------------
32 * # Prerequisites
33 * Abilities(Plugin Users):
34 * 1. Nothing special
35 * Abilities(Plugin Developers):
36 * 1. Basic knowledge on what the default RMMZ codebase does in general
37 * 2. Some RMMZ plugin development proficiency to fully utilize this
38 * plugin in intended ways
39 * (Basic knowledge on what RMMZ plugin development does in general
40 * with several easy, simple and small plugins written without
41 * nontrivial bugs up to 1000 LoC scale but still being inexperienced)
42 *----------------------------------------------------------------------------
43 * # Links
44 * This Plugin:
45 * 1. https://github.com/Double-X/DoubleX-RMMZ/blob/master/DoubleX%20RMMZ%20ES6%20Codebase.js
46 * Posts:
47 * 1. https://forums.rpgmakerweb.com/index.php?threads/is-a-plugin-rewriting-rmmz-codebase-into-es6-standard-a-good-idea.124928/
48 * 2. https://www.rpgmakercentral.com/topic/42504-is-a-plugin-rewriting-rmmz-codebase-into-es6-standard-a-good-idea/
49 * 3. https://rpgmaker.net/forums/topics/25326/?post=909885#post909885
50 * 4. https://xyphien.com/forums/threads/is-a-plugin-rewriting-rmmz-codebase-into-es6-standard-a-good-idea.9529/
51 *----------------------------------------------------------------------------
52 * # Instructions
53 * 1. THIS PLUGIN MUST BE PLACED ABOVE ALL THE OTHER PLUGINS
54 * 2. The version number of this plugin's supposed to be the same as that
55 * of the default RMMZ codebase, so this plugin must be outdated if
56 * those version numbers are indeed different
57 *----------------------------------------------------------------------------
58 * # Contributors
59 * Authors:
60 * 1. DoubleX
61 * Plugin Development Collaborators:
62 * - None So Far
63 * Bug Reporters:
64 * - None So Far
65 * Compatibility Issue Raisers:
66 * - None So Far
67 * Feature Requesters:
68 * - None So Far
69 *----------------------------------------------------------------------------
70 * # Changelog
71 * 0.9.5(GMT 1000 5-Aug-2020):
72 * 1. Rewritten the core parts into the ES6 standard
73 *----------------------------------------------------------------------------
74 * # Todo
75 * 1. Adds Array.prototype.filterFlat(with improved performance)
76 * 2. Adds Array.prototype.filterFlatMap(with improved performance)
77 * 3. Adds Array.prototype.filterMapReduce(with improved performance)
78 * 4. Adds Array.prototype.filterMapReduceRight(with improved
79 * performance)
80 * 5. Adds Array.prototype.filterReduce(with improved performance)
81 * 6. Adds Array.prototype.filterReduceRight(with improved performance)
82 * 7. Adds Array.prototype.flatFilter(with improved performance)
83 * 8. Adds Array.prototype.flatFilterReduce(with improved performance)
84 * 9. Adds Array.prototype.flatFilterReduceRight(with improved
85 * performance)
86 * 10. Adds Array.prototype.flatMapEvery(with improved performance)
87 * 11. Adds Array.prototype.flatMapFilter(with improved performance)
88 * 12. Adds Array.prototype.flatMapFilterReduce(with improved
89 * performance)
90 * 13. Adds Array.prototype.flatMapFilterReduceRight(with improved
91 * performance)
92 * 14. Adds Array.prototype.flatMapFind(with improved performance)
93 * 15. Adds Array.prototype.flatMapReduce(with improved performance)
94 * 16. Adds Array.prototype.flatMapReduceRight(with improved performance)
95 * 17. Adds Array.prototype.flatMapSome(with improved performance)
96 * 18. Adds Array.prototype.mapEvery(with improved performance)
97 * 19. Adds Array.prototype.mapFilterReduce(with improved performance)
98 * 20. Adds Array.prototype.mapFilterReduceRight(with improved
99 * performance)
100 * 21. Adds Array.prototype.mapFind(with improved performance)
101 * 22. Adds Array.prototype.mapReduceRight(with improved performance)
102 * 23. Adds Array.prototype.mapSome(with improved performance)
103 *============================================================================*/
104/*:
105 * @plugindesc (0.9.5)Helps plugin developers write their plugins into ES6
106 * standards in better ways, but such plugins will need this plugin to work
107 * @author DoubleX
108 *
109 * @help
110 *============================================================================
111 * ## Plugin Developers Info
112 *----------------------------------------------------------------------------
113 * # Aliasing functions/methods without prototyping on your side
114 * Please note that it doesn't work with built-in JavaScript prototypes
115 * like Array and String
116 * Do these 2 additional things when using ES6 class inheritance aliasing
117 * without directly typing prototypes:
118 * 1. Adds the following code right below a new class inheriting another
119 * one:
120 * - ES6ExtendedClassAlias.inherit(Klass);
121 * Where Klass is the new class inheriting another one
122 * 2. Adds the following code right below extending an existing class as
123 * a way to alias its methods:
124 * - ES6ExtendedClassAlias.update(Klass);
125 * Where Klass is the existing class being extended as a way to alias
126 * its methods
127 * Right now it doesn't work well with inheriting static functions in
128 * classes, so those in children classes should use
129 * ParentClass.staticFunc.call(this) instead of super.staticFunc()
130 * # New public APIs
131 * Input
132 * 1. isJustReleased(keyName)
133 * Returns if the specified key's just released right on this frame
134 * Array.prototype
135 * 1. fastMap(mapCallback, mapThis_)
136 * The same as map but is tested to be noticeably faster
137 * 2. fastMerge(arr)
138 * The same as concat except that fastMerge alters the original array
139 * instead of returning a new one
140 * 3. filterMap(filterCallback, mapCallback, filterThis_, mapThis_)
141 * The same as chaining filter with map except that the new array
142 * returned by filter will be mapped in place(.filter().map())
143 * 4. mapFilter(mapCallback, filterCallback, mapThis_, filterThis_)
144 * The same as chaining map with filter except that the new array
145 * returned by map will be filtered in place(.map().filter())
146 * 5. mapReduce(mapCallback, reduceCallback, initVal_, mapThis_, reduceThis_)
147 * The same as chaining map with reduce but is tested to be noticeably
148 * faster(.map().reduce())
149 * 6. isProperSubsetOf(arr)
150 * Returns if this array's a proper subset of the specified array
151 * 7. isProperSupersetOf(arr)
152 * Returns if this array's a proper superset of the specified array
153 * 8. isSupersetOf(arr)
154 * Returns if this array's a superset of the specified array
155 * 9. isSubsetOf(arr)
156 * Returns if this array's a subset of the specified array
157 * 10. isEmpty()
158 * Returns if this array's empty
159 * 11. symmetricDifference(arr)
160 * Returns the symmetric difference of this and the specified array
161 * 12. union(arr)
162 * Returns the union of this and the specified array
163 * 13. difference(arr)
164 * Returns the difference of this and the specified array
165 * 14. intersection(arr)
166 * Returns the intersection of this and the specified array
167 * 15. excludes(elem, fromI)
168 * Returns if this array doesn't include the specified element
169 * 16. clear()
170 * Empties the whole array
171 *============================================================================
172 */
173
174var DoubleX_RMMZ = DoubleX_RMMZ || {};
175DoubleX_RMMZ["ES6 Codebase"] = "0.9.5";
176
177/*============================================================================
178 * ## Plugin Implementations
179 * You need not edit this part as it's about how this plugin works
180 *----------------------------------------------------------------------------
181 * # Plugin Support Info:
182 * 1. Prerequisites
183 * - Basic knowledge on what the default RMMZ codebase does in general
184 * - Some RMMZ plugin development proficiency to fully comprehend this
185 * plugin
186 * (Basic knowledge on what RMMZ plugin development does in general
187 * with several easy, simple and small plugins written without
188 * nontrivial bugs up to 1000 LoC scale but still being
189 * inexperienced)
190 * 2. Parameter/Return value of type * means it might be of any type
191 * 3. Function signature with (**) means it might take any number of
192 * parameters of any type
193 * 4. Supposedly nullable variables are marked with the _ suffix in their
194 * names(but they can be sure to be non null in some cases)
195 * 5. Functions supposedly returning nullable values are marked with the
196 * _ suffix in their names(but their return values can be sure to be
197 * non null in some cases)
198 *----------------------------------------------------------------------------*/
199
200/*----------------------------------------------------------------------------
201 * # New class: ES6ExtendedClassAlias
202 * - Lets plugin developers alias ES6 classes without direct prototyping
203 *----------------------------------------------------------------------------*/
204
205// THIS CLASS ITSELF SHOULD NEVER EVER HAVE ANY CHILD CLASS
206class ES6ExtendedClassAlias {
207
208 static _inheritances = new Map();
209
210 /**
211 * Idempotent
212 * @author DoubleX @constructor @since 0.9.5 @version 0.9.5
213 */
214 constructor() {
215 throw new Error("ES6ExtendedClassAlias is a static class!");
216 } // constructor
217
218 /**
219 * Idempotent
220 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
221 * @param {Class} Child - The child class to inherit from its parent class
222 */
223 static inherit(Child) {
224 const childProto = Child.prototype;
225 const parentName = Object.getPrototypeOf(childProto).constructor.name;
226 this._inherit(Child, parentName);
227 } // inherit
228
229 /**
230 * Idempotent
231 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
232 * @param {Class} Parent - The parent class to update its children classes
233 */
234 static update(Parent) {
235 const parentName = Parent.prototype.constructor.name;
236 // There's no need to update anything if the passed class's no children
237 if (!this._inheritances.has(parentName)) return;
238 this._update(this._inheritances.get(parentName), Parent);
239 //
240 } // update
241
242 /**
243 * Idempotent
244 * @author DoubleX @since 0.9.5 @version 0.9.5
245 * @param {Class} Child - The child class to inherit from its parent class
246 * @param {string} parentName - The name of the parent of the Child class
247 */
248 static _inherit(Child, parentName) {
249 // So the parent class will know which classes are its children
250 if (this._inheritances.has(parentName)) {
251 // Set can only have unique elements so Child won't be duplicated
252 this._inheritances.get(parentName).add(Child);
253 //
254 } else this._inheritances.set(parentName, new Set([Child]));
255 //
256 } // _inherit
257
258 /**
259 * Idempotent
260 * @author DoubleX @since 0.9.5 @version 0.9.5
261 * @param {[Class]} children - The children classes of the parent class
262 * @param {Class} Parent - The parent class to update its children classes
263 */
264 static _update(children, Parent) {
265 this._updateProtoMethods(children, Parent.prototype);
266 this._updateStaticFuncs(children, Parent);
267 } // _update
268
269 /**
270 * Idempotent
271 * @author DoubleX @since 0.9.5 @version 0.9.5
272 * @param {[Class]} children - The children classes of the parent class
273 * @param {Prototype} parentProto - The parent class prototype
274 */
275 static _updateProtoMethods(children, parentProto) {
276 // So all the children will inherit the new rather than the old parent
277 children.forEach(Child => Child.prototype.__proto__ = parentProto);
278 //
279 } // _updateProtoMethods
280
281 /**
282 * Idempotent
283 * @author DoubleX @since 0.9.5 @version 0.9.5
284 * @param {[Class]} children - The children classes of the parent class
285 * @param {Class} Parent - The parent class to update its children classes
286 */
287 static _updateStaticFuncs(children, Parent) {
288 // So all children will inherit all new static functions from new parent
289 Object.getOwnPropertyNames(Parent).forEach(name => {
290 const desc = Object.getOwnPropertyDescriptor(Parent, name);
291 if (!desc || typeof desc.value !== "function") return;
292 const parentFunc = Parent[name];
293 children.forEach(Child => Child[name] = Child[name] || parentFunc);
294 });
295 //
296 } // _updateStaticFuncs
297
298} // ES6ExtendedClassAlias
299//
300
301/*----------------------------------------------------------------------------
302 * # Rewritten class: Graphics
303 * - Rewrites it into the ES6 standard
304 *----------------------------------------------------------------------------*/
305
306/**
307 * The static class that carries out graphics processing.
308 *
309 * @namespace
310 */
311class Graphics {
312
313 constructor() { throw new Error("This is a static class"); }
314
315 static initialize() {
316 // Edited to help plugins add more private variables in better ways
317 this._initPrivateVars();
318 //
319 // Edited to help plugins add more public variables in better ways
320 this._initPublicVars();
321 //
322 this._updateRealScale();
323 this._createAllElements();
324 this._disableContextMenu();
325 this._setupEventHandlers();
326 this._createPixiApp();
327 this._createEffekseerContext();
328 return !!this._app;
329 } // initialize
330
331 /**
332 * The PIXI.Application object.
333 *
334 * @readonly
335 * @type PIXI.Application
336 * @name Graphics.app
337 */
338 static get app() { return this._app; }
339
340 /**
341 * The context object of Effekseer.
342 *
343 * @readonly
344 * @type EffekseerContext
345 * @name Graphics.effekseer
346 */
347 static get effekseer() { return this._effekseer; }
348
349 /**
350 * Register a handler for tick events.
351 *
352 * @param {function} handler - The listener function to be added for updates.
353 */
354 static setTickHandler(handler) { this._tickHandler = handler; }
355
356 /**
357 * Starts the game loop.
358 */
359 static startGameLoop() { if (this._app) this._app.start(); }
360
361 /**
362 * Stops the game loop.
363 */
364 static stopGameLoop() { if (this._app) this._app.stop(); }
365
366 /**
367 * Sets the stage to be rendered.
368 *
369 * @param {Stage} stage - The stage object to be rendered.
370 */
371 static setStage(stage) { if (this._app) this._app.stage = stage; }
372
373 /**
374 * Shows the loading spinner.
375 */
376 static startLoading() {
377 if (document.getElementById("loadingSpinner")) return;
378 // Edited to help plugins alter loading spinner appending behaviors
379 this._appendLoadingSpinner();
380 //
381 } // startLoading
382
383 /**
384 * Erases the loading spinner.
385 *
386 * @returns {boolean} True if the loading spinner was active.
387 */
388 static endLoading() {
389 if (!document.getElementById("loadingSpinner")) return false;
390 // Edited to help plugins alter loading spinner removing behaviors
391 this._removeLoadingSpinner();
392 //
393 return true;
394 } // endLoading
395
396 /**
397 * Displays the error text to the screen.
398 *
399 * @param {string} name - The name of the error.
400 * @param {string} message - The message of the error.
401 * @param {Error} [error] - The error object.
402 */
403 static printError(name, message, error = null) {
404 if (!this._errorPrinter) this._createErrorPrinter();
405 this._errorPrinter.innerHTML = this._makeErrorHtml(name, message, error);
406 this._wasLoading = this.endLoading();
407 this._applyCanvasFilter();
408 } // printError
409
410 /**
411 * Displays a button to try to reload resources.
412 *
413 * @param {function} retry - The callback function to be called when the button
414 * is pressed.
415 */
416 static showRetryButton(retry) {
417 const button = document.createElement("button");
418 [button.id, button.innerHTML] = ["retryButton", "Retry"];
419 // [Note] stopPropagation() is required for iOS Safari.
420 // Edited to help plugins alter these listener function contents
421 button.ontouchstart = this._onRetryTouchStart.bind(this);
422 button.onclick = this._onRetry.bind(this, retry);
423 //
424 this._errorPrinter.appendChild(button);
425 button.focus();
426 } // showRetryButton
427
428 /**
429 * Erases the loading error text.
430 */
431 static eraseError() {
432 // Edited to help plugins alter error erasing behaviors with the printer
433 if (this._errorPrinter) this._eraseErrorWithPrinter();
434 //
435 this._clearCanvasFilter();
436 } // eraseError
437
438 /**
439 * Converts an x coordinate on the page to the corresponding
440 * x coordinate on the canvas area.
441 *
442 * @param {number} x - The x coordinate on the page to be converted.
443 * @returns {number} The x coordinate on the canvas area.
444 */
445 static pageToCanvasX(x) {
446 // Edited to help plugins alter the canvas x value with existing canvas
447 return this._canvas ? this._pageToExistingCanvasX(x) : 0;
448 //
449 } // pageToCanvasX
450
451 /**
452 * Converts a y coordinate on the page to the corresponding
453 * y coordinate on the canvas area.
454 *
455 * @param {number} y - The y coordinate on the page to be converted.
456 * @returns {number} The y coordinate on the canvas area.
457 */
458 static pageToCanvasY(y) {
459 // Edited to help plugins alter the canvas y value with existing canvas
460 return this._canvas ? this._pageToExistingCanvasY(y) : 0;
461 //
462 } // pageToCanvasY
463
464 /**
465 * Checks whether the specified point is inside the game canvas area.
466 *
467 * @param {number} x - The x coordinate on the canvas area.
468 * @param {number} y - The y coordinate on the canvas area.
469 * @returns {boolean} True if the specified point is inside the game canvas area.
470 */
471 static isInsideCanvas(x, y) {
472 return x >= 0 && x < this._width && y >= 0 && y < this._height;
473 } // isInsideCanvas
474
475 /**
476 * Shows the game screen.
477 */
478 static showScreen() { this._canvas.style.opacity = 1; }
479
480 /**
481 * Hides the game screen.
482 */
483 static hideScreen() { this._canvas.style.opacity = 0; }
484
485 /**
486 * Changes the size of the game screen.
487 *
488 * @param {number} width - The width of the game screen.
489 * @param {number} height - The height of the game screen.
490 */
491 static resize(width, height) {
492 [this._width, this._height] = [width, height];
493 /** @todo Check if it should be called with the same width and height */
494 this._updateAllElements();
495 //
496 } // resize
497
498 /**
499 * The width of the game screen.
500 *
501 * @type number
502 * @name Graphics.width
503 */
504 static get width() { return this._width; }
505 // Edited to dry up codes essentially being the identical knowledge
506 static set width(value) { this._updateDimen("_width", value); }
507 //
508
509 /**
510 * The height of the game screen.
511 *
512 * @type number
513 * @name Graphics.height
514 */
515 static get height() { return this._height; }
516 // Edited to dry up codes essentially being the identical knowledge
517 static set height(value) { this._updateDimen("_height", value); }
518 //
519
520 /**
521 * The default zoom scale of the game screen.
522 *
523 * @type number
524 * @name Graphics.defaultScale
525 */
526 static get defaultScale() { return this._defaultScale; }
527 static set defaultScale(value) {
528 // Edited to dry up codes essentially being the identical knowledge
529 this._updateDimen("_defaultScale", value);
530 //
531 } // defaultScale
532
533 static _createAllElements() {
534 this._createErrorPrinter();
535 this._createCanvas();
536 this._createLoadingSpinner();
537 this._createFPSCounter();
538 } // _createAllElements
539
540 static _updateAllElements() {
541 this._updateRealScale();
542 this._updateErrorPrinter();
543 this._updateCanvas();
544 this._updateVideo();
545 } // _updateAllElements
546
547 static _onTick(deltaTime) {
548 this._fpsCounter.startTick();
549 if (this._tickHandler) this._tickHandler(deltaTime);
550 this._app.render();
551 this.frameCount++;
552 this._fpsCounter.endTick();
553 } // _onTick
554
555 static _updateRealScale() {
556 // Edited to help plugins alter real scale updating behaviors
557 if (this._isUpdateStretchRealScale()) {
558 this._updateStretchRealScale();
559 } else this._updateDefaultRealScale();
560 //
561 } // _updateRealScale
562
563 static _stretchWidth() {
564 if (Utils.isMobileDevice()) return document.documentElement.clientWidth;
565 return window.innerWidth;
566 } // _stretchWidth
567
568 static _stretchHeight() {
569 if (!Utils.isMobileDevice()) return window.innerHeight;
570 // [Note] Mobile browsers often have special operations at the top and
571 // bottom of the screen.
572 const rate = Utils.isLocal() ? 1.0 : 0.9;
573 return document.documentElement.clientHeight * rate;
574 } // _stretchHeight
575
576 static _makeErrorHtml(name, message /*, error*/) {
577 // Edited to dry up codes essentially being the identical knowledge
578 const nameHTML = this._errorHTML("errorName", name);
579 const msgHTML = this._errorHTML("errorMessage", message);
580 return nameHTML + msgHTML;
581 //
582 } // _makeErrorHtml
583
584 static _defaultStretchMode() {
585 return Utils.isNwjs() || Utils.isAtsumaru() || Utils.isMobileDevice();
586 } // _defaultStretchMode
587
588 static _createErrorPrinter() {
589 // Edited to help plugins alter the error printer in better ways
590 this._errorPrinter = this._newErrorPrinter();
591 //
592 document.body.appendChild(this._errorPrinter);
593 } // _createErrorPrinter
594
595 static _updateErrorPrinter() {
596 // Edited to help plugins alter the existing error printer in better way
597 if (this._errorPrinter) this._updateExistingErrorPrinter();
598 //
599 } // _updateErrorPrinter
600
601 static _createCanvas() {
602 // Edited to help plugins alter the canvas in better ways
603 this._canvas = this._newCanvas();
604 this._updateExistingCanvas();
605 //
606 document.body.appendChild(this._canvas);
607 } // _createCanvas
608
609 // Edited to help plugins update the existing canvas in better ways
610 static _updateCanvas() { if (this._canvas) this._updateExistingCanvas(); }
611 //
612
613 static _updateVideo() {
614 const w = this._width * this._realScale;
615 const h = this._height * this._realScale;
616 Video.resize(w, h);
617 } // _updateVideo
618
619 static _createLoadingSpinner() {
620 // Edited to help plugins create the loading spinner in better ways
621 this._loadingSpinner = this._newLoadingSpinner();
622 //
623 } // _createLoadingSpinner
624
625 static _createFPSCounter() { this._fpsCounter = new Graphics.FPSCounter(); }
626
627 static _centerElement(element) {
628 const { style, width, height } = element;
629 [style.position, style.margin] = ["absolute", "auto"];
630 style.top = style.left = style.right = style.bottom = 0;
631 style.width = `${width * this._realScale}px`;
632 style.height = `${height * this._realScale}px`;
633 } // _centerElement
634
635 static _disableContextMenu() {
636 const oncontextmenu = () => false;
637 document.body.getElementsByTagName("*").forEach(elem => {
638 elem.oncontextmenu = oncontextmenu;
639 });
640 } // _disableContextMenu
641
642 static _applyCanvasFilter() {
643 // Edited to help plugins apply existing canvas filter in better ways
644 if (this._canvas) this._applyExistingCanvasFilter();
645 //
646 } // _applyCanvasFilter
647
648 static _clearCanvasFilter() {
649 // Edited to help plugins clear existing canvas filter in better ways
650 if (this._canvas) this._clearExistingCanvasFilter();
651 //
652 } // _clearCanvasFilter
653
654 static _setupEventHandlers() {
655 window.addEventListener("resize", this._onWindowResize.bind(this));
656 document.addEventListener("keydown", this._onKeyDown.bind(this));
657 } // _setupEventHandlers
658
659 static _onWindowResize() { this._updateAllElements(); }
660
661 static _onKeyDown(event) {
662 // Edited to help plugins alter key events in better ways
663 if (this._hasNoKeyEvent(event)) return;
664 const { keyCode } = event;
665 for (const [keyCodeFunc, eventFunc] of this._keyEvents) {
666 if (keyCodeFunc() !== keyCode) continue;
667 event.preventDefault();
668 return eventFunc();
669 }
670 //
671 } // _onKeyDown
672
673 static _switchFPSCounter() { this._fpsCounter.switchMode(); }
674
675 static _switchStretchMode() {
676 this._stretchEnabled = !this._stretchEnabled;
677 /** @todo Check if it should be called if there's nothing to stretch */
678 this._updateAllElements();
679 //
680 } // _switchStretchMode
681
682 static _switchFullScreen() {
683 if (this._isFullScreen()) return this._cancelFullScreen();
684 this._requestFullScreen();
685 } // _switchFullScreen
686
687 static _isFullScreen() {
688 if (document.fullScreenElement) return true;
689 return document.mozFullScreen || document.webkitFullscreenElement;
690 } // _isFullScreen
691
692 static _requestFullScreen() {
693 const elem = document.body;
694 if (elem.requestFullScreen) return elem.requestFullScreen();
695 if (elem.mozRequestFullScreen) return elem.mozRequestFullScreen();
696 if (!elem.webkitRequestFullScreen) return;
697 elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
698 } // _requestFullScreen
699
700 static _cancelFullScreen() {
701 if (document.cancelFullScreen) return document.cancelFullScreen();
702 if (document.mozCancelFullScreen) return document.mozCancelFullScreen();
703 if (!document.webkitCancelFullScreen) return;
704 document.webkitCancelFullScreen();
705 } // _cancelFullScreen
706
707 static _createPixiApp() {
708 /** @todo Thinks of if at least logging the catch will be better */
709 try {
710 // Edited to help plugins creating pixi app in better ways
711 this._createPixiAppWithoutRescue();
712 //
713 } catch (e) { this._app = null; }
714 //
715 } // _createPixiApp
716
717 static _setupPixi() {
718 PIXI.utils.skipHello();
719 PIXI.settings.GC_MAX_IDLE = 600;
720 } // _setupPixi
721
722 static _createEffekseerContext() {
723 // Edited to help plugins creating effekseer context in better ways
724 if (this._isCreateEffekseerContext()) this._tryCreateEffekseerContext();
725 //
726 } // _createEffekseerContext
727
728 /**
729 * Initializes all graphics private variables
730 * Idempotent
731 * @author DoubleX @since 0.9.5 @version 0.9.5
732 */
733 static _initPrivateVars() {
734 this._width = this._height = 0;
735 this._defaultScale = this._realScale = 1;
736 this._errorPrinter = this._tickHandler = this._canvas = null;
737 this._fpsCounter = this._loadingSpinner = null;
738 this._stretchEnabled = this._defaultStretchMode();
739 this._app = this._effekseer = null;
740 this._wasLoading = false;
741 // Added to help plugins alter key events in better ways
742 this._initKeyEvents();
743 //
744 } // _initPrivateVars
745
746 /**
747 * Adds a new private variable to help plugins alter key events
748 * Idempotent
749 * @author DoubleX @since 0.9.5 @version 0.9.5
750 */
751 static _initKeyEvents() {
752 this._keyEvents = new Map();
753 const fpsKeyFunc = this._switchFPSCounterKey.bind(this);
754 this._keyEvents.set(fpsKeyFunc, this._switchFPSCounter.bind(this));
755 const stretchKeyFunc = this._switchStretchModeKey.bind(this);
756 this._keyEvents.set(stretchKeyFunc, this._switchStretchMode.bind(this));
757 const fullScreenKeyFn = this._switchFullScreenKey.bind(this);
758 this._keyEvents.set(fullScreenKeyFn, this._switchFullScreen.bind(this));
759 } // _initKeyEvents
760
761 /**
762 * Nullipotent
763 * @author DoubleX @since 0.9.5 @version 0.9.5
764 * @enum @returns {number} - The code of the switch FPS counter key
765 */
766 static _switchFPSCounterKey() { return 113; /* F2 */ }
767
768 /**
769 * Nullipotent
770 * @author DoubleX @since 0.9.5 @version 0.9.5
771 * @enum @returns {number} - The code of the switch stretch mode key
772 */
773 static _switchStretchModeKey() { return 114; /* F3 */ }
774
775 /**
776 * Nullipotent
777 * @author DoubleX @since 0.9.5 @version 0.9.5
778 * @enum @returns {number} - The code of the switch full screen key
779 */
780 static _switchFullScreenKey() { return 115; /* F4 */ }
781
782 /**
783 * This function should be called after calling _initPrivateVars
784 * Idempotent
785 * @author DoubleX @since 0.9.5 @version 0.9.5
786 */
787 static _initPublicVars() {
788 /**
789 * The total frame count of the game screen.
790 *
791 * @type number
792 * @name Graphics.frameCount
793 */
794 this.frameCount = 0;
795 /**
796 * The width of the window display area.
797 *
798 * @type number
799 * @name Graphics.boxWidth
800 */
801 this.boxWidth = this._width;
802 /**
803 * The height of the window display area.
804 *
805 * @type number
806 * @name Graphics.boxHeight
807 */
808 this.boxHeight = this._height;
809 } // _initPublicVars
810
811 /**
812 * This function shouldn't be called with an existing loading spinner
813 * @author DoubleX @since 0.9.5 @version 0.9.5
814 */
815 static _appendLoadingSpinner() {
816 document.body.appendChild(this._loadingSpinner);
817 } // _appendLoadingSpinner
818
819 /**
820 * This function shouldn't be called without an existing loading spinner
821 * @author DoubleX @since 0.9.5 @version 0.9.5
822 */
823 static _removeLoadingSpinner() {
824 document.body.removeChild(this._loadingSpinner);
825 } // _removeLoadingSpinner
826
827 /**
828 * It's supposed to be the ontouchstart event of the retry button
829 * @author DoubleX @since 0.9.5 @version 0.9.5
830 */
831 static _onRetryTouchStart(e) { e.stopPropagation(); }
832
833 /**
834 * It's supposed to be the onclick event of the retry button
835 * @author DoubleX @since 0.9.5 @version 0.9.5
836 * @param {()} retry - The callback to be called when the button's pressed
837 */
838 static _onRetry(retry) {
839 this.eraseError();
840 retry();
841 } // _onRetry
842
843 /**
844 * This function shouldn't be called without an existing error printer
845 * Idempotent
846 * @author DoubleX @since 0.9.5 @version 0.9.5
847 */
848 static _eraseErrorWithPrinter() {
849 this._errorPrinter.innerHTML = this._makeErrorHtml();
850 if (this._wasLoading) this.startLoading();
851 } // _eraseErrorWithPrinter
852
853 /**
854 * Hotspot/Nullipotent
855 * @author DoubleX @since 0.9.5 @version 0.9.5
856 * @param {number} x - The x coordinate on the page to be converted
857 * @returns {number} The x coordinate on the canvas area
858 */
859 static _pageToExistingCanvasX(x) {
860 return Math.round((x - this._canvas.offsetLeft) / this._realScale);
861 } // _pageToExistingCanvasX
862
863 /**
864 * Hotspot/Nullipotent
865 * @author DoubleX @since 0.9.5 @version 0.9.5
866 * @param {number} y - The y coordinate on the page to be converted
867 * @returns {number} The y coordinate on the canvas area
868 */
869 static _pageToExistingCanvasY(y) {
870 return Math.round((y - this._canvas.offsetTop) / this._realScale);
871 } // _pageToExistingCanvasY
872
873 /**
874 * Updates all graphics elements if the specified dimension's indeed changed
875 * Idempotent
876 * @author DoubleX @since 0.9.5 @version 0.9.5
877 * @enum @param {string} dimen - The name of the dimension to be updated
878 * @param {*} val - The updated value of the dimension
879 */
880 static _updateDimen(dimen, val) {
881 if (this[dimen] === val) return;
882 this[dimen] = val;
883 this._updateAllElements();
884 } // _updateDimen
885
886 /**
887 * Nullipotent
888 * @author DoubleX @since 0.9.5 @version 0.9.5
889 * @returns {boolean} If the real scale's updated with the stretch enabled
890 */
891 static _isUpdateStretchRealScale() {
892 return this._stretchEnabled && this._width > 0 && this._height > 0;
893 } // _isUpdateStretchRealScale
894
895 /**
896 * Sets the real scale with the stretch enabled
897 * Idempotent
898 * @author DoubleX @since 0.9.5 @version 0.9.5
899 */
900 static _updateStretchRealScale() {
901 this._realScale = this._stretchRealScale();
902 window.scrollTo(0, 0);
903 } // _updateStretchRealScale
904
905 /**
906 * Nullipotent
907 * @author DoubleX @since 0.9.5 @version 0.9.5
908 * @returns {number} The real scale with the stretch enabled
909 */
910 static _stretchRealScale() {
911 const h = this._stretchWidth() / this._width;
912 const v = this._stretchHeight() / this._height;
913 return Math.min(h, v);
914 } // _stretchRealScale
915
916 /**
917 * Sets the real scale without the stretch enabled nor dimension to stretch
918 * Idempotent
919 * @author DoubleX @since 0.9.5 @version 0.9.5
920 */
921 static _updateDefaultRealScale() { this._realScale = this._defaultScale; }
922
923 /**
924 * Creates a new document with a specified id as well as returning the html
925 * Nullipotent
926 * @author DoubleX @since 0.9.5 @version 0.9.5
927 * @returns {string} The html of the specified error display portion
928 */
929 static _errorHTML(id, html) {
930 const div = document.createElement("div");
931 [div.id, div.innerHTML] = [id, Utils.escapeHtml(html || "")];
932 return div.outerHTML;
933 } // _errorHTML
934
935 /**
936 * Creates a new document with id errorPrinter as the error printer
937 * Nullipotent
938 * @author DoubleX @since 0.9.5 @version 0.9.5
939 * @returns {DOM} The error printer document
940 */
941 static _newErrorPrinter() {
942 const errorPrinter = document.createElement("div");
943 errorPrinter.id = "errorPrinter";
944 errorPrinter.innerHTML = this._makeErrorHtml();
945 return errorPrinter;
946 } // _newErrorPrinter
947
948 /**
949 * This function shouldn't be called without an existing error printer
950 * Idempotent
951 * @author DoubleX @since 0.9.5 @version 0.9.5
952 */
953 static _updateExistingErrorPrinter() {
954 const { style } = this._errorPrinter;
955 /** @todo Figures out where do 640 and 100 come from respectively */
956 style.width = `${640 * this._realScale}px`;
957 style.height = `${100 * this._realScale}px`;
958 //
959 } // _updateExistingErrorPrinter
960
961 /**
962 * Creates new canvas document with id gameCanvas
963 * Nullipotent
964 * @author DoubleX @since 0.9.5 @version 0.9.5
965 * @returns {Canvas} The graphics canvas document
966 */
967 static _newCanvas() {
968 const canvas = document.createElement("canvas");
969 canvas.id = "gameCanvas";
970 return canvas;
971 } // _newCanvas
972
973 /**
974 * This function shouldn't be called without existing canvas
975 * Idempotent
976 * @author DoubleX @since 0.9.5 @version 0.9.5
977 */
978 static _updateExistingCanvas() {
979 [this._canvas.width, this._canvas.height] = [this._width, this._height];
980 this._canvas.style.zIndex = 1;
981 this._centerElement(this._canvas);
982 } // _updateExistingCanvas
983
984 /**
985 * Creates a new document as the loading spinner with id loadingSpinner
986 * Nullipotent
987 * @author DoubleX @since 0.9.5 @version 0.9.5
988 * @returns {DOM} The loading spinner document
989 */
990 static _newLoadingSpinner() {
991 const loadingSpinner = document.createElement("div");
992 loadingSpinner.id = "loadingSpinner";
993 loadingSpinner.appendChild(this._newLoadingSpinnerImg());
994 return loadingSpinner;
995 } // _newLoadingSpinner
996
997 /**
998 * Creates a new dom as loading spinner image with id loadingSpinnerImage
999 * Nullipotent
1000 * @author DoubleX @since 0.9.5 @version 0.9.5
1001 * @returns {DOM} The loading spinner document
1002 */
1003 static _newLoadingSpinnerImg() {
1004 const loadingSpinnerImage = document.createElement("div");
1005 loadingSpinnerImage.id = "loadingSpinnerImage";
1006 return loadingSpinnerImage;
1007 } // _newLoadingSpinnerImg
1008
1009 /**
1010 * This function shouldn't be called without existing canvas
1011 * Idempotent
1012 * @author DoubleX @since 0.9.5 @version 0.9.5
1013 */
1014 static _applyExistingCanvasFilter() {
1015 const { style } = this._canvas, filter = "blur(8px)";
1016 style.opacity = 0.5;
1017 [style.filter, style.webkitFilter] = [filter, filter];
1018 } // _applyExistingCanvasFilter
1019
1020 /**
1021 * This function shouldn't be called without existing canvas
1022 * Idempotent
1023 * @author DoubleX @since 0.9.5 @version 0.9.5
1024 */
1025 static _clearExistingCanvasFilter() {
1026 const { style } = this._canvas, filter = "";
1027 [style.opacity, style.filter, style.webkitFilter] = [1, filter, filter];
1028 } // _clearExistingCanvasFilter
1029
1030 /**
1031 * Nullipotent
1032 * @author DoubleX @since 0.9.5 @version 0.9.5
1033 * @param {Event} event - The onkeydown event to be checked against
1034 * @returns {boolean} If the event might trigger functions to be called
1035 */
1036 static _hasNoKeyEvent(event) { return event.ctrlKey || event.altKey; }
1037
1038 /**
1039 * This function shouldn't be called without a try and catch
1040 * Idempotent
1041 * @author DoubleX @since 0.9.5 @version 0.9.5
1042 */
1043 static _createPixiAppWithoutRescue() {
1044 this._setupPixi();
1045 this._app = this._pixiApp();
1046 } // _createPixiAppWithoutRescue
1047
1048 /**
1049 * Nullipotent
1050 * @author DoubleX @since 0.9.5 @version 0.9.5
1051 * @returns {PIXI.Application} The graphics pixi application
1052 */
1053 static _pixiApp() {
1054 const app = new PIXI.Application({
1055 view: this._canvas,
1056 autoStart: false
1057 });
1058 app.ticker.remove(app.render, app);
1059 app.ticker.add(this._onTick, this);
1060 return app;
1061 } // _pixiApp
1062
1063 /**
1064 * Nullipotent
1065 * @author DoubleX @since 0.9.5 @version 0.9.5
1066 * @returns {boolean} If the effekseer context can be created
1067 */
1068 static _isCreateEffekseerContext() { return this._app && window.effekseer; }
1069
1070 /**
1071 * Removes the pixi application if it fails to create an effekseer context
1072 * Idempotent
1073 * @author DoubleX @since 0.9.5 @version 0.9.5
1074 */
1075 static _tryCreateEffekseerContext() {
1076 /** @todo Thinks of if at least logging the catch will be better */
1077 try {
1078 this._effekseer = this._effekseerContextWithoutRescue();
1079 } catch (e) { this._app = null; }
1080 //
1081 } // _tryCreateEffekseerContext
1082
1083 /**
1084 * This function shouldn't be called without a try and catch
1085 * Nullipotent
1086 * @author DoubleX @since 0.9.5 @version 0.9.5
1087 * @returns {EffekseerContext} The graphics effekseer context
1088 */
1089 static _effekseerContextWithoutRescue() {
1090 const context = effekseer.createContext();
1091 if (context) context.init(this._app.renderer.gl);
1092 return context;
1093 } // _effekseerContextWithoutRescue
1094
1095} // Graphics
1096
1097/*----------------------------------------------------------------------------
1098 * # Rewritten class: Graphics.FPSCounter
1099 * - Rewrites it into the ES6 standard
1100 *----------------------------------------------------------------------------*/
1101
1102//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1103// FPSCounter
1104//
1105// This is based on Darsain's FPSMeter which is under the MIT license.
1106// The original can be found at https://github.com/Darsain/fpsmeter.
1107Graphics.FPSCounter = class {
1108
1109 constructor() {
1110 [this._tickCount, this._frameTime, this._frameStart] = [0, 100, 0];
1111 [this._lastLoop, this._showFps] = [performance.now() - 100, true];
1112 this.fps = this.duration = 0;
1113 this._createElements();
1114 this._update();
1115 } // constructor
1116
1117 startTick() { this._frameStart = performance.now(); }
1118
1119 endTick() {
1120 const time = performance.now(), thisFrameTime = time - this._lastLoop;
1121 /** @todo Figures out where does 12 come from */
1122 this._frameTime += (thisFrameTime - this._frameTime) / 12;
1123 //
1124 this.fps = 1000 / this._frameTime;
1125 this.duration = Math.max(0, time - this._frameStart);
1126 this._lastLoop = time;
1127 /** @todo Figures out where does 15 come from */
1128 if (this._tickCount++ % 15 === 0) this._update();
1129 //
1130 } // endTick
1131
1132 switchMode() {
1133 // Edited to help plugins alter meter modes in better ways
1134 this._switchMode();
1135 //
1136 this._update();
1137 } // switchMode
1138
1139 _createElements() {
1140 // Edited to help plugins create elements in better ways
1141 this._labelDiv = this._newLabelDiv();
1142 this._numberDiv = this._newNumberDiv();
1143 this._boxDiv = this._newBoxDiv(this._labelDiv, this._numberDiv);
1144 //
1145 document.body.appendChild(this._boxDiv);
1146 } // _createElements
1147
1148 _update() {
1149 const count = this._showFps ? this.fps : this.duration;
1150 this._labelDiv.textContent = this._showFps ? "FPS" : "ms";
1151 this._numberDiv.textContent = count.toFixed(0);
1152 } // _update
1153
1154 /**
1155 * Switches the fps counter following none -> fps -> duration -> none
1156 * Idempotent
1157 * @author DoubleX @since 0.9.5 @version 0.9.5
1158 */
1159 _switchMode() {
1160 if (this._boxDiv.style.display === "none") return this._switchToFPS();
1161 this._showFps ? this._switchToDuration() : this._switchToNone();
1162 } // _switchMode
1163
1164 /**
1165 * Switches the fps counter to show the fps
1166 * Idempotent
1167 * @author DoubleX @since 0.9.5 @version 0.9.5
1168 */
1169 _switchToFPS() {
1170 [this._boxDiv.style.display, this._showFps] = ["block", true];
1171 } // _switchToFPS
1172
1173 /**
1174 * Switches the fps counter to show the number of ms elapsed in this frame
1175 * Idempotent
1176 * @author DoubleX @since 0.9.5 @version 0.9.5
1177 */
1178 _switchToDuration() { this._showFps = false; }
1179
1180 /**
1181 * Switches the fps counter to show nothing
1182 * Idempotent
1183 * @author DoubleX @since 0.9.5 @version 0.9.5
1184 */
1185 _switchToNone() { this._boxDiv.style.display = "none"; }
1186
1187 /**
1188 * Creates a new fps counter box div document with id fpsCounterBox
1189 * Nullipotent
1190 * @author DoubleX @since 0.9.5 @version 0.9.5
1191 * @param {Div} labelDiv - The fps counter label div document
1192 * @param {Div} numberDiv - The fps counter number div document
1193 * @returns {DOM} The fps counter box div document
1194 */
1195 _newBoxDiv(labelDiv, numberDiv) {
1196 const boxDiv = document.createElement("div");
1197 [boxDiv.id, boxDiv.style.display] = ["fpsCounterBox", "none"];
1198 boxDiv.appendChild(labelDiv);
1199 boxDiv.appendChild(numberDiv);
1200 return boxDiv;
1201 } // _newBoxDiv
1202
1203 /**
1204 * Creates a new fps counter label div document with id fpsCounterLabel
1205 * Nullipotent
1206 * @author DoubleX @since 0.9.5 @version 0.9.5
1207 * @returns {DOM} The fps counter label div document
1208 */
1209 _newLabelDiv() {
1210 const labelDiv = document.createElement("div");
1211 labelDiv.id = "fpsCounterLabel";
1212 return labelDiv;
1213 } // _newLabelDiv
1214
1215 /**
1216 * Creates a new fps counter number div document with id fpsCounterNumber
1217 * Nullipotent
1218 * @author DoubleX @since 0.9.5 @version 0.9.5
1219 * @returns {DOM} The fps counter number div document
1220 */
1221 _newNumberDiv() {
1222 const numberDiv = document.createElement("div");
1223 numberDiv.id = "fpsCounterNumber";
1224 return numberDiv;
1225 } // _newNumberDiv
1226
1227}; // Graphics.FPSCounter
1228
1229/*----------------------------------------------------------------------------
1230 * # Rewritten class: Input
1231 * - Rewrites it into the ES6 standard
1232 *----------------------------------------------------------------------------*/
1233
1234/**
1235 * The static class that handles input data from the keyboard and gamepads.
1236 *
1237 * @namespace
1238 */
1239class Input {
1240
1241 constructor() { throw new Error("This is a static class"); }
1242
1243 /**
1244 * Initializes the input system.
1245 */
1246 static initialize() {
1247 this.clear();
1248 this._setupEventHandlers();
1249 } // initialize
1250
1251 /**
1252 * The wait time of the key repeat in frames.
1253 *
1254 * @type number
1255 */
1256 static keyRepeatWait = 24;
1257
1258 /**
1259 * The interval of the key repeat in frames.
1260 *
1261 * @type number
1262 */
1263 static keyRepeatInterval = 6;
1264
1265 /**
1266 * A hash table to convert from a virtual key code to a mapped key name.
1267 *
1268 * @type Object
1269 */
1270 static keyMapper = {
1271 9: "tab", // tab
1272 13: "ok", // enter
1273 16: "shift", // shift
1274 17: "control", // control
1275 18: "control", // alt
1276 27: "escape", // escape
1277 32: "ok", // space
1278 33: "pageup", // pageup
1279 34: "pagedown", // pagedown
1280 37: "left", // left arrow
1281 38: "up", // up arrow
1282 39: "right", // right arrow
1283 40: "down", // down arrow
1284 45: "escape", // insert
1285 81: "pageup", // Q
1286 87: "pagedown", // W
1287 88: "escape", // X
1288 90: "ok", // Z
1289 96: "escape", // numpad 0
1290 98: "down", // numpad 2
1291 100: "left", // numpad 4
1292 102: "right", // numpad 6
1293 104: "up", // numpad 8
1294 120: "debug" // F9
1295 }; // keyMapper
1296
1297 /**
1298 * A hash table to convert from a gamepad button to a mapped key name.
1299 *
1300 * @type Object
1301 */
1302 static gamepadMapper = {
1303 0: "ok", // A
1304 1: "cancel", // B
1305 2: "shift", // X
1306 3: "menu", // Y
1307 4: "pageup", // LB
1308 5: "pagedown", // RB
1309 12: "up", // D-pad up
1310 13: "down", // D-pad down
1311 14: "left", // D-pad left
1312 15: "right" // D-pad right
1313 }; // gamepadMapper
1314
1315 /**
1316 * Clears all the input data.
1317 */
1318 static clear() {
1319 [this._currentState, this._previousState] = [new Map(), new Map()];
1320 [this._gamepadStates, this._latestButton] = [[], null];
1321 this._pressedTime = this._dir4 = this._dir8 = 0;
1322 [this._preferredAxis, this._date, this._virtualButton] = ["", 0, null];
1323 // Added to support the isJustReleased static function
1324 this._isJustReleased = new Map();
1325 //
1326 } // clear
1327
1328 /**
1329 * Updates the input data.
1330 */
1331 static update() {
1332 this._pollGamepads();
1333 // Edited to help plugins update the current states in better ways
1334 this._updateLatestButton();
1335 this._currentState.forEach(this._updateCurrentState, this);
1336 //
1337 if (this._virtualButton) this._updateVirtualClick();
1338 this._updateDirection();
1339 } // update
1340
1341 /**
1342 * Checks whether a key is currently pressed down.
1343 *
1344 * @param {string} keyName - The mapped name of the key.
1345 * @returns {boolean} True if the key is pressed.
1346 */
1347 static isPressed(keyName) {
1348 if (this._isEscCompatiblePressed(keyName)) return true;
1349 return !!this._currentState.get(keyName);
1350 } // isPressed
1351
1352 /**
1353 * Checks whether a key is just pressed.
1354 *
1355 * @param {string} keyName - The mapped name of the key.
1356 * @returns {boolean} True if the key is triggered.
1357 */
1358 static isTriggered(keyName) {
1359 if (this._isEscCompatiblePressed(keyName)) return true;
1360 return this._latestButton === keyName && this._pressedTime === 0;
1361 } // isTriggered
1362
1363 /**
1364 * Checks whether a key is just pressed or a key repeat occurred.
1365 *
1366 * @param {string} keyName - The mapped name of the key.
1367 * @returns {boolean} True if the key is repeated.
1368 */
1369 static isRepeated(keyName) {
1370 if (this._isEscCompatiblePressed(keyName)) return true;
1371 if (this._latestButton !== keyName) return false;
1372 if (this._pressedTime === 0) return true;
1373 if (this._pressedTime < this.keyRepeatWait) return false;
1374 return this._pressedTime % this.keyRepeatInterval === 0;
1375 } // isRepeated
1376
1377 /**
1378 * Checks whether a key is kept depressed.
1379 *
1380 * @param {string} keyName - The mapped name of the key.
1381 * @returns {boolean} True if the key is long-pressed.
1382 */
1383 static isLongPressed(keyName) {
1384 if (this._isEscCompatiblePressed(keyName)) return true;
1385 if (this._latestButton !== keyName) return false;
1386 return this._pressedTime >= this.keyRepeatWait;
1387 } // isLongPressed
1388
1389 /**
1390 * The four direction value as a number of the numpad, or 0 for neutral.
1391 *
1392 * @readonly
1393 * @type number
1394 * @name Input.dir4
1395 */
1396 static get dir4() { return this._dir4; }
1397
1398 /**
1399 * The eight direction value as a number of the numpad, or 0 for neutral.
1400 *
1401 * @readonly
1402 * @type number
1403 * @name Input.dir8
1404 */
1405 static get dir8() { return this._dir8; }
1406
1407 /**
1408 * The time of the last input in milliseconds.
1409 *
1410 * @readonly
1411 * @type number
1412 * @name Input.date
1413 */
1414 static get date() { return this._date; }
1415
1416 static virtualClick(buttonName) { this._virtualButton = buttonName; }
1417
1418 /**
1419 * Nullipotent
1420 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1421 * @enum @param {string} keyName - The mapped name of the key
1422 * @returns {boolean} If the key's just released right on this frame
1423 */
1424 static isJustReleased(keyName) {
1425 if (this._isEscCompatiblePressed(keyName)) return true;
1426 return this._isJustReleased.has(keyName);
1427 } // isJustReleased
1428
1429 static _setupEventHandlers() {
1430 document.addEventListener("keydown", this._onKeyDown.bind(this));
1431 document.addEventListener("keyup", this._onKeyUp.bind(this));
1432 window.addEventListener("blur", this._onLostFocus.bind(this));
1433 } // _setupEventHandlers
1434
1435 static _onKeyDown(event) {
1436 const { keyCode } = event;
1437 if (this._shouldPreventDefault(keyCode)) event.preventDefault();
1438 // Edited to help plugins alter clear keys in better ways
1439 if (this._shouldClear(keyCode)) this.clear(); // Numlock
1440 //
1441 const buttonName = this.keyMapper[keyCode];
1442 if (buttonName) this._currentState.set(buttonName, true);
1443 } // _onKeyDown
1444
1445 static _shouldPreventDefault(keyCode) {
1446 switch (keyCode) {
1447 case 8: // backspace
1448 case 9: // tab
1449 case 33: // pageup
1450 case 34: // pagedown
1451 case 37: // left arrow
1452 case 38: // up arrow
1453 case 39: // right arrow
1454 case 40: // down arrow
1455 return true;
1456 }
1457 return false;
1458 } // _shouldPreventDefault
1459
1460 static _onKeyUp(event) {
1461 const buttonName = this.keyMapper[event.keyCode];
1462 if (buttonName) this._currentState.set(buttonName, false);
1463 } // _onKeyUp
1464
1465 static _onLostFocus() { this.clear(); }
1466
1467 static _pollGamepads() {
1468 if (!navigator.getGamepads) return;
1469 const gamepads = navigator.getGamepads();
1470 // Edited to help plugins poll gamepads in better ways
1471 if (gamepads) gamepads.forEach(this._pollGamepad, this);
1472 //
1473 } // _pollGamepads
1474
1475 static _updateGamepadState(gamepad) {
1476 const { index, buttons, axes } = gamepad;
1477 const lastState = this._gamepadStates[index] || [];
1478 // Edited to help plugins alter new gamepad states in better ways
1479 this._gamepadStates[index] = this._newGamepadStates(buttons, axes);
1480 //
1481 this._gamepadStates[index].forEach((ns, i) => {
1482 if (ns === lastState[i]) return;
1483 const buttonName = this.gamepadMapper[i];
1484 if (buttonName) this._currentState.set(buttonName, ns);
1485 });
1486 } // _updateGamepadState
1487
1488 static _updateDirection() {
1489 let [x, y] = [this._signX(), this._signY()];
1490 this._dir8 = this._makeNumpadDirection(x, y);
1491 if (x !== 0 && y !== 0) {
1492 this._preferredAxis === "x" ? y = 0 : x = 0;
1493 } else if (x !== 0) {
1494 this._preferredAxis = "y";
1495 } else if (y !== 0) this._preferredAxis = "x";
1496 this._dir4 = this._makeNumpadDirection(x, y);
1497 } // _updateDirection
1498
1499 // Edited to dry up codes essentially being the identical knowledge
1500 static _signX() { return this._sign("right") - this._sign("left"); }
1501 //
1502
1503 // Edited to dry up codes essentially being the identical knowledge
1504 static _signY() { return this._sign("down") - this._sign("up"); }
1505 //
1506
1507 static _makeNumpadDirection(x, y) {
1508 return x === 0 && y === 0 ? 0 : 5 - y * 3 + x;
1509 } // _makeNumpadDirection
1510
1511 static _isEscapeCompatible(keyName) {
1512 return keyName === "cancel" || keyName === "menu";
1513 } // _isEscapeCompatible
1514
1515 /**
1516 * Updates the latest pressed button states
1517 * Hotspot/Idempotent
1518 * @author DoubleX @since 0.9.5 @version 0.9.5
1519 */
1520 static _updateLatestButton() {
1521 if (this._currentState.get(this._latestButton)) {
1522 this._pressedTime++;
1523 } else this._latestButton = null;
1524 } // _updateLatestButton
1525
1526 /**
1527 * Updates the current state of all input keys
1528 * Hotspot/Idempotent
1529 * @author DoubleX @since 0.9.5 @version 0.9.5
1530 * @enum @param {boolean} keyState - If the key's currently pressed
1531 * @enum @param {string} keyName - The mapped name of the key
1532 */
1533 static _updateCurrentState(keyState, keyName) {
1534 this._updateLatestState(keyName);
1535 this._previousState.set(keyName, keyState);
1536 } // _updateCurrentState
1537
1538 /**
1539 * Updates the latest state of all input keys
1540 * Hotspot/Idempotent
1541 * @author DoubleX @since 0.9.5 @version 0.9.5
1542 * @enum @param {string} keyName - The mapped name of the key
1543 */
1544 static _updateLatestState(keyName) {
1545 if (this._isJustPressed(keyName)) return this._onStartPress(keyName);
1546 // Added to support the isJustReleased static function
1547 if (this._isKeyJustReleased(keyName)) {
1548 this._isJustReleased.set(keyName, true);
1549 } else this._isJustReleased.delete(keyName);
1550 //
1551 } // _updateLatestState
1552
1553 /**
1554 * Hotspot/Nullipotent
1555 * @author DoubleX @since 0.9.5 @version 0.9.5
1556 * @enum @param {string} keyName - The mapped name of the key
1557 * @returns {boolean} If the key's just pressed right on this frame
1558 */
1559 static _isJustPressed(keyName) {
1560 if (!this._currentState.get(keyName)) return false;
1561 return !this._previousState.get(keyName);
1562 } // _isJustPressed
1563
1564 /**
1565 * Updates the current input states with the input key that's just pressed
1566 * Idempotent
1567 * @author DoubleX @since 0.9.5 @version 0.9.5
1568 * @enum @param {string} keyName - The mapped name of the key
1569 */
1570 static _onStartPress(keyName) {
1571 [this._latestButton, this._pressedTime] = [keyName, 0];
1572 this._date = Date.now();
1573 this._isJustReleased.delete(keyName);
1574 } // _onStartPress
1575
1576 /**
1577 * Hotspot/Nullipotent
1578 * @author DoubleX @since 0.9.5 @version 0.9.5
1579 * @enum @param {string} keyName - The mapped name of the key
1580 * @returns {boolean} If the key's just released right on this frame
1581 */
1582 static _isKeyJustReleased(keyName) {
1583 if (this._currentState.get(keyName)) return false;
1584 return this._previousState.get(keyName);
1585 } // _isKeyJustReleased
1586
1587 /**
1588 * Updates the current input states with virtual button that's just clicked
1589 * Hotspot/Idempotent
1590 * @author DoubleX @since 0.9.5 @version 0.9.5
1591 */
1592 static _updateVirtualClick() {
1593 this._latestButton = this._virtualButton;
1594 [this._pressedTime, this._virtualButton] = [0, null];
1595 } // _updateVirtualClick
1596
1597 /**
1598 * Hotspot/Nullipotent
1599 * @author DoubleX @since 0.9.5 @version 0.9.5
1600 * @enum @param {string} keyName - The mapped name of the key
1601 * @returns {boolean} If the specified key should be regarded as pressed
1602 */
1603 static _isEscCompatiblePressed(keyName) {
1604 return this._isEscapeCompatible(keyName) && this.isPressed("escape");
1605 } // _isEscCompatiblePressed
1606
1607 /**
1608 * Nullipotent
1609 * @author DoubleX @since 0.9.5 @version 0.9.5
1610 * @enum @param {string} keyName - The mapped name of the key
1611 * @returns {boolean} If the pressed key should clear all input states
1612 */
1613 static _shouldClear(keyCode) { return keyCode === 144; }
1614
1615 /**
1616 * Updates the existing and connected gamepad state
1617 * Hotspot/Idempotent
1618 * @author DoubleX @since 0.9.5 @version 0.9.5
1619 * @enum @param {Gamepad} gamepad - The gamepad to be polled
1620 */
1621 static _pollGamepad(gamepad) {
1622 if (gamepad && gamepad.connected) this._updateGamepadState(gamepad);
1623 } // _pollGamepad
1624
1625 /**
1626 * Hotspot/Nullipotent
1627 * @author DoubleX @since 0.9.5 @version 0.9.5
1628 * @enum @param {[GamepadButton]} buttons - The list of the gamepad buttons
1629 * @enum @param {[number]} axes - The amounts of the gamepad axes directions
1630 * @returns {[boolean]} The list of new gamepad button states
1631 */
1632 static _newGamepadStates(buttons, axes) {
1633 const [newState, threshold] = [[], 0.5];
1634 newState[12] = newState[13] = newState[14] = newState[15] = false;
1635 buttons.forEach((button, i) => newState[i] = button.pressed);
1636 if (axes[1] < -threshold) {
1637 newState[12] = true; // up
1638 } else if (axes[1] > threshold) newState[13] = true; // down
1639 if (axes[0] < -threshold) {
1640 newState[14] = true; // left
1641 } else if (axes[0] > threshold) newState[15] = true; // right
1642 return newState;
1643 } // _newGamepadStates
1644
1645 /**
1646 * Hotspot/Nullipotent
1647 * @author DoubleX @since 0.9.5 @version 0.9.5
1648 * @enum @param {string} keyName - The mapped name of the key
1649 * @enum @returns {number} 1 for the direction of this key and 0 for not
1650 */
1651 static _sign(keyName) { return this.isPressed(keyName) ? 1 : 0; }
1652
1653} // Input
1654
1655/*----------------------------------------------------------------------------
1656 * # Edit class: Array
1657 * - Adds some new array functions
1658 *----------------------------------------------------------------------------*/
1659
1660(function($) {
1661
1662 /**
1663 * Potential Hotspot/Nullipotent
1664 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1665 * @memberof JsExtensions
1666 * @param {(*, <T>, index, [<T>]) -> *} mapCallback - The callback in the
1667 * Array map method
1668 * @param {*?} mapThis_ - The context of mapCallback
1669 * @returns {Array} - The fully mapped array from this
1670 */
1671 $.fastMap = function(mapCallback, mapThis_) {
1672 if (this == null) throw new TypeError('this is null or not defined');
1673 if (typeof mapCallback !== 'function') {
1674 throw new TypeError(mapCallback + ' is not a function');
1675 }
1676 const newArray = [];
1677 // forEach is tested to be the fastest among sandboxes including RMMV
1678 this.forEach((elem, i) => {
1679 // It's ok to call undefined context with previously bound callbacks
1680 newArray.push(mapCallback.call(mapThis_, elem, i, this));
1681 //
1682 });
1683 //
1684 return newArray;
1685 }; // $.fastMap
1686
1687 /**
1688 * concat array that can be changed in place will lead to needless throwaway
1689 * push can't be applied to merge extremely long arrays so fastMerge is made
1690 * This method alters the original array(this) as it merges another in place
1691 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1692 * @memberof JsExtensions
1693 * @param {[*]} arr - The array to be merged
1694 * @returns {This} The original array merged with another array in place
1695 */
1696 $.fastMerge = function(arr) {
1697 // forEach is tested to be the fastest among sandboxes including RMMV
1698 arr.forEach(elem => this.push(elem));
1699 // array.forEach(this.push, this) can't be used as forEach has > 1 args
1700 return this;
1701 }; // $.fastMerge
1702
1703 /**
1704 * Chaining filter with map will lead to a new redundantly throwaway Array
1705 * This method doesn't support the thisArg argument in mapCallback
1706 * Potential Hotspot/Nullipotent
1707 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1708 * @memberof JsExtensions
1709 * @param {(*, <T>, index, [<T>]) -> boolean} filterCallback - The callback
1710 * in the Array
1711 * filter method
1712 * @param {(*, *, index) -> *} mapCallback - The callback in the Array map
1713 * method
1714 * @param {*?} filterThis_ - The context of filterCallback
1715 * @param {*?} mapThis_ - The context of mapCallback
1716 * @returns {Array} - The fully filtered then mapped array from this
1717 */
1718 $.filterMap = function(filterCallback, mapCallback, filterThis_, mapThis_) {
1719 if (this == null) throw new TypeError('this is null or not defined');
1720 if (typeof filterCallback !== 'function') {
1721 throw new TypeError(filterCallback + ' is not a function');
1722 } else if (typeof mapCallback !== 'function') {
1723 throw new TypeError(mapCallback + ' is not a function');
1724 }
1725 const newArray = [];
1726 // forEach is tested to be the fastest among sandboxes including RMMV
1727 this.forEach((elem, i) => {
1728 // It's ok to call undefined context with previously bound callbacks
1729 if (!filterCallback.call(filterThis_, elem, i, this)) return;
1730 newArray.push(mapCallback.call(mapThis_, elem, i));
1731 //
1732 });
1733 //
1734 return newArray;
1735 }; // $.filterMap
1736
1737 /**
1738 * Chaining map with filter will lead to a new redundantly throwaway Array
1739 * This method doesn't support the thisArg argument in filterCallback
1740 * Potential Hotspot/Nullipotent
1741 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1742 * @memberof JsExtensions
1743 * @param {(*, <T>, Index, [<T>]) -> *} mapCallback - The callback in the
1744 * Array map method
1745 * @param {(*, *, Index) -> Boolean} filterCallback - The callback in the
1746 * Array filter method
1747 * @param {*?} mapThis_ - The context of mapCallback
1748 * @param {*?} filterThis_ - The context of filterCallback
1749 * @returns {Array} - The fully mapped then filtered array from this
1750 */
1751 $.mapFilter = function(mapCallback, filterCallback, mapThis_, filterThis_) {
1752 if (this == null) throw new TypeError('this is null or not defined');
1753 if (typeof mapCallback !== 'function') {
1754 throw new TypeError(mapCallback + ' is not a function');
1755 } else if (typeof filterCallback !== 'function') {
1756 throw new TypeError(filterCallback + ' is not a function');
1757 }
1758 const newArray = [];
1759 // forEach is tested to be the fastest among sandboxes including RMMV
1760 this.forEach((elem, i) => {
1761 // It's ok to call undefined context with previously bound callbacks
1762 var mappedElem = mapCallback.call(mapThis_, elem, i, this);
1763 if (!filterCallback.call(filterThis_, mappedElem, i)) return;
1764 //
1765 newArray.push(mappedElem);
1766 });
1767 //
1768 return newArray;
1769 }; // $.mapFilter
1770
1771 /**
1772 * Chaining map with reduce will lead to a new redundantly throwaway Array
1773 * This method doesn't support the thisArg argument in reduceCallback
1774 * Potential Hotspot/Nullipotent
1775 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1776 * @memberof JsExtensions
1777 * @param {(*, <T>, Index, [<T>]) -> *} mapCallback - The callback in the
1778 * Array map method
1779 * @param {(*, *, *, Index) -> *} reduceCallback - The callback in the Array
1780 * reduce method
1781 * @param {*?} initVal_ - The initial value of reduceCallback
1782 * @param {*?} mapThis_ - The context of mapCallback
1783 * @param {*?} reduceThis_ - The context of reduceCallback
1784 * @returns {Array} - The fully mapped then reduced array result from this
1785 */
1786 $.mapReduce = function(mapCallback, reduceCallback, initVal_, mapThis_, reduceThis_) {
1787 if (this == null) throw new TypeError('this is null or not defined');
1788 const l = this.length, hasInitVal = initVal_ !== undefined;
1789 if (typeof mapCallback !== 'function') {
1790 throw new TypeError(mapCallback + ' is not a function');
1791 } else if (typeof reduceCallback !== 'function') {
1792 throw new TypeError(reduceCallback + ' is not a function');
1793 } else if (l <= 0 && !hasInitVal) {
1794 throw new TypeError('Reduce of empty array with no initial value');
1795 }
1796 if (hasInitVal) {
1797 let val = initVal_;
1798 // forEach is tested to be fastest among sandboxes including RMMV
1799 this.forEach((elem, i) => {
1800 // It's ok to call undefined context with already bound callback
1801 var mappedElem = mapCallback.call(mapThis_, elem, i, this);
1802 val = reduceCallback.call(reduceThis_, val, mappedElem, i);
1803 //
1804 });
1805 //
1806 return val;
1807 }
1808 /** @todo Uses forEach without checking if (i === 0) to be faster */
1809 let val = this[0], i = 1;
1810 while (i < l) {
1811 // It's ok to call undefined context with already bound callback
1812 var mappedElem = mapCallback.call(mapThis_, this[i], i, this);
1813 val = reduceCallback.call(reduceThis_, val, mappedElem, i);
1814 //
1815 i++;
1816 }
1817 //
1818 return val;
1819 }; // $.mapReduce
1820
1821 /**
1822 * Potential Hotspot/Nullipotent
1823 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1824 * @memberof JsExtensions
1825 * @param {Array} arr - The array to be checked against
1826 * @returns {boolean} If this's a proper subset of the specified array
1827 */
1828 $.isProperSubsetOf = function(arr) {
1829 return this.isSubsetOf(arr) && !arr.isSubsetOf(this);
1830 }; // $.isProperSubsetOf
1831
1832 /**
1833 * Potential Hotspot/Nullipotent
1834 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1835 * @memberof JsExtensions
1836 * @param {Array} arr - The array to be checked against
1837 * @returns {boolean} If this's a proper superset of the specified array
1838 */
1839 $.isProperSupersetOf = function(arr) {
1840 return this.isSupersetOf(arr) && !arr.isSupersetOf(this);
1841 }; // $.isProperSupersetOf
1842
1843 /**
1844 * Potential Hotspot/Nullipotent
1845 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1846 * @memberof JsExtensions
1847 * @param {Array} arr - The array to be checked against
1848 * @returns {boolean} If this's a superset of the specified array
1849 */
1850 $.isSupersetOf = function(arr) { return arr.isSubsetOf(this); };
1851
1852 /**
1853 * Potential Hotspot/Nullipotent
1854 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1855 * @memberof JsExtensions
1856 * @param {Array} arr - The array to be checked against
1857 * @returns {boolean} If this's a subset of the specified array
1858 */
1859 $.isSubsetOf = function(arr) { return this.difference(arr).isEmpty(); };
1860
1861 /**
1862 * Potential Hotspot/Nullipotent
1863 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1864 * @memberof JsExtensions
1865 * @returns {boolean} If this array's empty
1866 */
1867 $.isEmpty = function() { return this.length <= 0; };
1868
1869 /**
1870 * Potential Hotspot/Nullipotent
1871 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1872 * @memberof JsExtensions
1873 * @param {Array} arr - The array to have symmetric difference with
1874 * @returns {Array} The symmetric difference of this and the specified array
1875 */
1876 $.symmetricDifference = function(arr) {
1877 return this.difference(arr).union(arr.difference(this));
1878 }; // $.symmetricDifference
1879
1880 /**
1881 * Potential Hotspot/Nullipotent
1882 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1883 * @memberof JsExtensions
1884 * @param {Array} arr - The array to have union with this array
1885 * @returns {Array} The union of this and the specified array
1886 */
1887 $.union = function(arr) { return this.concat(arr.difference(this)); };
1888
1889 /**
1890 * Potential Hotspot/Nullipotent
1891 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1892 * @memberof JsExtensions
1893 * @param {Array} arr - The array to have difference with this array
1894 * @returns {Array} The difference of this and the specified array
1895 */
1896 $.difference = function(arr) {
1897 return this.filter(elem => arr.excludes(elem));
1898 }; // $.difference
1899
1900 /**
1901 * Potential Hotspot/Nullipotent
1902 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1903 * @memberof JsExtensions
1904 * @param {Array} arr - The array to have intersection with this array
1905 * @returns {Array} The intersection of this and the specified array
1906 */
1907 $.intersection = function(arr) {
1908 // The 2nd argument of includes doesn't match with that of filter
1909 return this.filter(elem => arr.includes(elem));
1910 //
1911 }; // $.intersection
1912
1913 /**
1914 * Potential Hotspot/Nullipotent
1915 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1916 * @memberof JsExtensions
1917 * @param {*} elem - The element to be checked against
1918 * @param {index} fromI - The index in this at which to begin searching
1919 * @returns {boolean} If this array doesn't have the specified element
1920 */
1921 $.excludes = function(elem, fromI) { return !this.includes(elem, fromI); };
1922
1923 /**
1924 * Potential Hotspot/Idempotent
1925 * @author DoubleX @interface @since 0.9.5 @version 0.9.5
1926 * @memberof JsExtensions
1927 */
1928 $.clear = function() { this.length = 0; }
1929
1930})(Array.prototype);
1931
1932/*----------------------------------------------------------------------------
1933 * # Rewritten class: JsonEx
1934 * - Rewrites it into the ES6 standard
1935 *----------------------------------------------------------------------------*/
1936
1937//-----------------------------------------------------------------------------
1938/**
1939 * The static class that handles JSON with object information.
1940 *
1941 * @namespace
1942 */
1943class JsonEx {
1944
1945 constructor() { throw new Error("This is a static class"); }
1946
1947 /**
1948 * The maximum depth of objects.
1949 *
1950 * @type number
1951 * @default 100
1952 */
1953 static maxDepth = 100;
1954
1955 /**
1956 * Converts an object to a JSON string with object information.
1957 *
1958 * @param {object} object - The object to be converted.
1959 * @returns {string} The JSON string.
1960 */
1961 static stringify(object) { return JSON.stringify(this._encode(object, 0)); }
1962
1963 /**
1964 * Parses a JSON string and reconstructs the corresponding object.
1965 *
1966 * @param {string} json - The JSON string.
1967 * @returns {object} The reconstructed object.
1968 */
1969 static parse(json) { return this._decode(JSON.parse(json)); }
1970
1971 /**
1972 * Makes a deep copy of the specified object.
1973 *
1974 * @param {object} object - The object to be copied.
1975 * @returns {object} The copied object.
1976 */
1977 static makeDeepCopy(object) { return this.parse(this.stringify(object)); }
1978
1979 static _encode(value, depth) {
1980 // [Note] The handling code for circular references in certain versions of
1981 // MV has been removed because it was too complicated and expensive.
1982 if (depth >= this.maxDepth) throw new Error("Object too deep");
1983 // Edited to help plugins alter encode behaviors in better ways
1984 if (this._isValObj(value)) return this._encodeValObj(value, depth);
1985 //
1986 return value;
1987 } // _encode
1988
1989 static _decode(value) {
1990 // Edited to help plugins alter decode behaviors in better ways
1991 return this._isValObj(value) ? this._decodeValObj(value) : value;
1992 //
1993 } // _decode
1994
1995 /**
1996 * Pure function
1997 * @author DoubleX @since 0.9.5 @version 0.9.5
1998 * @param {*} val - The value object to be encoded
1999 * @param {number} depth - The current depth of the encoded value object
2000 * @returns {*} The fully encoded value object
2001 */
2002 static _encodeValObj(val, depth) {
2003 const constructorName = val.constructor.name;
2004 if (constructorName !== "Object" && constructorName !== "Array") {
2005 val["@"] = constructorName;
2006 }
2007 Object.entries(val).forEach(([k, v]) => {
2008 val[k] = this._encode(v, depth + 1);
2009 });
2010 return val;
2011 } // _encodeValObj
2012
2013 /**
2014 * Pure function
2015 * @author DoubleX @since 0.9.5 @version 0.9.5
2016 * @param {*} val - The value to be encoded or decoded
2017 * @returns {boolean} If the value's indeed a value object
2018 */
2019 static _isValObj(val) {
2020 const type = Object.prototype.toString.call(val);
2021 return type === "[object Object]" || type === "[object Array]";
2022 } // _isValObj
2023
2024 /**
2025 * Pure function
2026 * @author DoubleX @since 0.9.5 @version 0.9.5
2027 * @param {*} val - The value object to be decoded
2028 * @returns {*} The fully decoded value object
2029 */
2030 static _decodeValObj(val) {
2031 const constructorName = val["@"];
2032 if (constructorName) {
2033 const constructor = window[constructorName];
2034 if (constructor) Object.setPrototypeOf(val, constructor.prototype);
2035 }
2036 Object.entries(val).forEach(([k, v]) => val[k] = this._decode(v));
2037 return val;
2038 } // _decodeValObj
2039
2040} // JsonEx
2041
2042/*----------------------------------------------------------------------------
2043 * # Rewritten class: TouchInput
2044 * - Rewrites it into the ES6 standard
2045 *----------------------------------------------------------------------------*/
2046
2047//-----------------------------------------------------------------------------
2048/**
2049 * The static class that handles input data from the mouse and touchscreen.
2050 *
2051 * @namespace
2052 */
2053class TouchInput {
2054
2055 constructor() { throw new Error("This is a static class"); }
2056
2057 /**
2058 * Initializes the touch system.
2059 */
2060 static initialize() {
2061 this.clear();
2062 this._setupEventHandlers();
2063 } // initialize
2064
2065 /**
2066 * The wait time of the pseudo key repeat in frames.
2067 *
2068 * @type number
2069 */
2070 static keyRepeatWait = 24;
2071
2072 /**
2073 * The interval of the pseudo key repeat in frames.
2074 *
2075 * @type number
2076 */
2077 static keyRepeatInterval = 6;
2078
2079 /**
2080 * The threshold number of pixels to treat as moved.
2081 *
2082 * @type number
2083 */
2084 static moveThreshold = 10;
2085
2086 static clear() {
2087 this._mousePressed = this._screenPressed = false;
2088 [this._pressedTime, this._clicked] = [0, false];
2089 this._newState = this._createNewState();
2090 this._currentState = this._createNewState();
2091 this._x = this._y = this._triggerX = this._triggerY = 0;
2092 [this._moved, this._date] = [false, 0];
2093 } // clear
2094
2095 static update() {
2096 this._currentState = this._newState;
2097 this._newState = this._createNewState();
2098 this._clicked = this._currentState.released && !this._moved;
2099 if (this.isPressed()) this._pressedTime++;
2100 } // update
2101
2102 /**
2103 * Checks whether the mouse button or touchscreen has been pressed and
2104 * released at the same position.
2105 *
2106 * @returns {boolean} True if the mouse button or touchscreen is clicked.
2107 */
2108 static isClicked() { return this._clicked; }
2109
2110 /**
2111 * Checks whether the mouse button or touchscreen is currently pressed down.
2112 *
2113 * @returns {boolean} True if the mouse button or touchscreen is pressed.
2114 */
2115 static isPressed() { return this._mousePressed || this._screenPressed; }
2116
2117 /**
2118 * Checks whether the left mouse button or touchscreen is just pressed.
2119 *
2120 * @returns {boolean} True if the mouse button or touchscreen is triggered.
2121 */
2122 static isTriggered() { return this._currentState.triggered; }
2123
2124 /**
2125 * Checks whether the left mouse button or touchscreen is just pressed
2126 * or a pseudo key repeat occurred.
2127 *
2128 * @returns {boolean} True if the mouse button or touchscreen is repeated.
2129 */
2130 static isRepeated() {
2131 if (!this.isPressed()) return false;
2132 if (this._currentState.triggered) return true;
2133 if (this._pressedTime < this.keyRepeatWait) return false;
2134 return this._pressedTime % this.keyRepeatInterval === 0;
2135 } // isRepeated
2136
2137 /**
2138 * Checks whether the left mouse button or touchscreen is kept depressed.
2139 *
2140 * @returns {boolean} True if the left mouse button or touchscreen is long-pressed.
2141 */
2142 static isLongPressed() {
2143 return this.isPressed() && this._pressedTime >= this.keyRepeatWait;
2144 } // isLongPressed
2145
2146 /**
2147 * Checks whether the right mouse button is just pressed.
2148 *
2149 * @returns {boolean} True if the right mouse button is just pressed.
2150 */
2151 static isCancelled() { return this._currentState.cancelled; }
2152
2153 /**
2154 * Checks whether the mouse or a finger on the touchscreen is moved.
2155 *
2156 * @returns {boolean} True if the mouse or a finger on the touchscreen is moved.
2157 */
2158 static isMoved() { return this._currentState.moved; }
2159
2160 /**
2161 * Checks whether the mouse is moved without pressing a button.
2162 *
2163 * @returns {boolean} True if the mouse is hovered.
2164 */
2165 static isHovered() { return this._currentState.hovered; }
2166
2167 /**
2168 * Checks whether the left mouse button or touchscreen is released.
2169 *
2170 * @returns {boolean} True if the mouse button or touchscreen is released.
2171 */
2172 static isReleased() { return this._currentState.released; }
2173
2174 /**
2175 * The horizontal scroll amount.
2176 *
2177 * @readonly
2178 * @type number
2179 * @name TouchInput.wheelX
2180 */
2181 static get wheelX() { return this._currentState.wheelX; }
2182
2183 /**
2184 * The vertical scroll amount.
2185 *
2186 * @readonly
2187 * @type number
2188 * @name TouchInput.wheelY
2189 */
2190 static get wheelY() { return this._currentState.wheelY; }
2191
2192 /**
2193 * The x coordinate on the canvas area of the latest touch event.
2194 *
2195 * @readonly
2196 * @type number
2197 * @name TouchInput.x
2198 */
2199 static get x() { return this._x; }
2200
2201 /**
2202 * The y coordinate on the canvas area of the latest touch event.
2203 *
2204 * @readonly
2205 * @type number
2206 * @name TouchInput.y
2207 */
2208 static get y() { return this._y; }
2209
2210 /**
2211 * The time of the last input in milliseconds.
2212 *
2213 * @readonly
2214 * @type number
2215 * @name TouchInput.date
2216 */
2217 static get date() { return this._date; }
2218
2219 static _createNewState() {
2220 return {
2221 triggered: false,
2222 cancelled: false,
2223 moved: false,
2224 hovered: false,
2225 released: false,
2226 wheelX: 0,
2227 wheelY: 0
2228 };
2229 } // _createNewState
2230
2231 static _setupEventHandlers() {
2232 // Edited to help plugins alter event handlers in better ways
2233 this._setupMouseEventHandlers();
2234 this._setupTouchEventHandlers();
2235 //
2236 window.addEventListener("blur", this._onLostFocus.bind(this));
2237 } // _setupEventHandlers
2238
2239 static _onMouseDown(event) {
2240 switch (event.button) {
2241 case 0: return this._onLeftButtonDown(event);
2242 case 1: return this._onMiddleButtonDown(event);
2243 case 2: this._onRightButtonDown(event);
2244 }
2245 } // _onMouseDown
2246
2247 static _onLeftButtonDown(event) {
2248 // Edited to dry up codes essentially being the identical knowledge
2249 const xy = this._pageToCanvasXY(event);
2250 if (!Graphics.isInsideCanvas(...xy)) return;
2251 //
2252 // Edited to help plugins alter left button down behaviors in better way
2253 this._onLeftButtonDownInsideCanvas(...xy);
2254 //
2255 } // _onLeftButtonDown
2256
2257 static _onMiddleButtonDown(/*event*/) {}
2258
2259 static _onRightButtonDown(event) {
2260 // Edited to dry up codes essentially being the identical knowledge
2261 const xy = this._pageToCanvasXY(event);
2262 if (Graphics.isInsideCanvas(...xy)) this._onCancel(...xy);
2263 //
2264 } // _onRightButtonDown
2265
2266 static _onMouseMove(event) {
2267 // Edited to dry up codes essentially being the identical knowledge
2268 const xy = this._pageToCanvasXY(event);
2269 if (this._mousePressed) return this._onMove(...xy);
2270 if (Graphics.isInsideCanvas(...xy)) this._onHover(...xy);
2271 //
2272 } // _onMouseMove
2273
2274 static _onMouseUp(event) {
2275 // Edited to help plugins alter left button up behaviors in better ways
2276 if (event.button === 0) this._onLeftButtonUp(event);
2277 //
2278 } // _onMouseUp
2279
2280 static _onWheel(event) {
2281 this._newState.wheelX += event.deltaX;
2282 this._newState.wheelY += event.deltaY;
2283 event.preventDefault();
2284 } // _onWheel
2285
2286 static _onTouchStart(event) {
2287 const { changedTouches, touches } = event;
2288 changedTouches.forEach(touch => {
2289 // Edited to help plugins alter touches inside canvas in better ways
2290 const xy = this._pageToCanvasXY(touch);
2291 if (!Graphics.isInsideCanvas(...xy)) return;
2292 this._onTouchStartInsideCanvas(touches, ...xy);
2293 //
2294 });
2295 /** @todo Extracts this conditional into a well-named static function */
2296 if (!window.cordova && !window.navigator.standalone) return;
2297 //
2298 event.preventDefault();
2299 } // _onTouchStart
2300
2301 static _onTouchMove(event) {
2302 event.changedTouches.forEach(this._onMoveTouch, this);
2303 } // _onTouchMove
2304
2305 static _onTouchEnd(event) {
2306 event.changedTouches.forEach(this._onReleaseTouch, this);
2307 } // _onTouchEnd
2308
2309 static _onTouchCancelfunction(/*event*/) { this._screenPressed = false; }
2310
2311 static _onLostFocus() { this.clear(); }
2312
2313 static _onTrigger(x, y) {
2314 this._newState.triggered = true;
2315 [this._x, this._y, this._triggerX, this._triggerY] = [x, y, x, y];
2316 [this._moved, this._date] = [false, Date.now()];
2317 } // _onTrigger
2318
2319 // Edited to dry up codes essentially being the identical knowledge
2320 static _onCancel(x, y) { this._onUpdateNewState("cancelled", x, y); }
2321 //
2322
2323 static _onMove(x, y) {
2324 // Edited to help plugins alter the on move event in better ways
2325 if (this._isMoved(x, y)) this._moved = true;
2326 if (this._moved) this._onUpdateNewState("_moved", x, y);
2327 //
2328 } // _onMove
2329
2330 // Edited to dry up codes essentially being the identical knowledge
2331 static _onHover(x, y) { this._onUpdateNewState("hovered", x, y); }
2332 //
2333
2334 // Edited to dry up codes essentially being the identical knowledge
2335 static _onRelease(x, y) { this._onUpdateNewState("released", x, y); }
2336 //
2337
2338 /**
2339 * Setups all mouse event handlers of this touch input static class
2340 * Idempotent
2341 * @author DoubleX @since 0.9.5 @version 0.9.5
2342 */
2343 static _setupMouseEventHandlers() {
2344 document.addEventListener("mousedown", this._onMouseDown.bind(this));
2345 document.addEventListener("mousemove", this._onMouseMove.bind(this));
2346 document.addEventListener("mouseup", this._onMouseUp.bind(this));
2347 document.addEventListener("wheel", this._onWheel.bind(this), {
2348 passive: false
2349 });
2350 } // _setupMouseEventHandlers
2351
2352 /**
2353 * Setups all touch event handlers of this touch input static class
2354 * Idempotent
2355 * @author DoubleX @since 0.9.5 @version 0.9.5
2356 */
2357 static _setupTouchEventHandlers() {
2358 const pf = { passive: false };
2359 document.addEventListener("touchstart", this._onTouchStart.bind(this), pf);
2360 document.addEventListener("touchmove", this._onTouchMove.bind(this), pf);
2361 document.addEventListener("touchend", this._onTouchEnd.bind(this));
2362 document.addEventListener("touchcancel", this._onTouchCancel.bind(this));
2363 } // _setupTouchEventHandlers
2364
2365 /**
2366 * Triggers the left button down event inside the canvas x and y positions
2367 * Idempotent
2368 * @author DoubleX @since 0.9.5 @version 0.9.5
2369 * @param {number} x - The page x position of the touch event
2370 * @param {number} y - The page y position of the touch event
2371 */
2372 static _onLeftButtonDownInsideCanvas(x, y) {
2373 [this._mousePressed, this._pressedTime] = [true, 0];
2374 this._onTrigger(x, y);
2375 } // _onLeftButtonDownInsideCanvas
2376
2377 /**
2378 * Triggers the specified the left button up event
2379 * Idempotent
2380 * @author DoubleX @since 0.9.5 @version 0.9.5
2381 * @param {Event} event - The specified left button up event
2382 */
2383 static _onLeftButtonUp(event) {
2384 this._mousePressed = false;
2385 this._onRelease(...this._pageToCanvasXY(event));
2386 } // _onLeftButtonUp
2387
2388 /**
2389 * Triggers the touch start event inside canvas for the specified touches
2390 * Idempotent
2391 * @author DoubleX @since 0.9.5 @version 0.9.5
2392 * @param {[Touch]} touches - The specified touches from the specified event
2393 * @param {number} x - The page x position of the touch event
2394 * @param {number} y - The page y position of the touch event
2395 */
2396 static _onTouchStartInsideCanvas(touches, x, y) {
2397 [this._screenPressed, this._pressedTime] = [true, 0];
2398 touches.length >= 2 ? this._onCancel(x, y) : this._onTrigger(x, y);
2399 event.preventDefault();
2400 } // _onTouchStartInsideCanvas
2401
2402 /**
2403 * Triggers the touch move event for the specified touch
2404 * Potential Hotspot/Idempotent
2405 * @author DoubleX @since 0.9.5 @version 0.9.5
2406 * @param {Touch} touch - The specified touch from the specified event
2407 */
2408 static _onMoveTouch(touch) { this._onMove(...this._pageToCanvasXY(touch)); }
2409
2410 /**
2411 * Triggers the touch release event for the specified touch
2412 * Idempotent
2413 * @author DoubleX @since 0.9.5 @version 0.9.5
2414 * @param {Touch} touch - The specified touch from the specified event
2415 */
2416 static _onReleaseTouch(touch) {
2417 this._screenPressed = false;
2418 this._onRelease(...this._pageToCanvasXY(touch));
2419 } // _onReleaseTouch
2420
2421 /**
2422 * Potential Hotspot/Nullipotent
2423 * @author DoubleX @since 0.9.5 @version 0.9.5
2424 * @param {Event|Touch} eventTouch - The specified event or its touch
2425 * @returns {[number]} The converted x and y canvas positions
2426 */
2427 static _pageToCanvasXY(eventTouch) {
2428 return [
2429 Graphics.pageToCanvasX(eventTouch.pageX),
2430 Graphics.pageToCanvasY(eventTouch.pageY)
2431 ];
2432 } // _pageToCanvasXY
2433
2434 /**
2435 * Potential Hotspot/Nullipotent
2436 * @author DoubleX @since 0.9.5 @version 0.9.5
2437 * @param {number} x - The page x position of the touch event
2438 * @param {number} y - The page y position of the touch event
2439 * @returns {boolean} If the detected touch event's indeed a movement
2440 */
2441 static _isMoved(x, y) {
2442 const dx = Math.abs(x - this._triggerX);
2443 const dy = Math.abs(y - this._triggerY);
2444 return dx > this.moveThreshold || dy > this.moveThreshold;
2445 } // _isMoved
2446
2447 /**
2448 * Updates the specified new states as well as the current x and y positions
2449 * Idempotent
2450 * @author DoubleX @since 0.9.5 @version 0.9.5
2451 * @enum @param {string} state - The name of the new state to be updated
2452 * @param {number} x - The page x position of the touch event
2453 * @param {number} y - The page y position of the touch event
2454 */
2455 static _onUpdateNewState(state, x, y) {
2456 [this._newState[state], this._x, this._y] = [true, x, y];
2457 } // _onUpdateNewState
2458
2459} // TouchInput
2460
2461/*----------------------------------------------------------------------------
2462 * # Rewritten class: Utils
2463 * - Rewrites it into the ES6 standard
2464 *----------------------------------------------------------------------------*/
2465
2466//-----------------------------------------------------------------------------
2467/**
2468 * The static class that defines utility methods.
2469 *
2470 * @namespace
2471 */
2472class Utils {
2473
2474 constructor() { throw new Error("This is a static class"); }
2475
2476 /**
2477 * The name of the RPG Maker. "MZ" in the current version.
2478 *
2479 * @type string
2480 * @constant
2481 */
2482 static RPGMAKER_NAME = "MZ";
2483
2484 /**
2485 * The version of the RPG Maker.
2486 *
2487 * @type string
2488 * @constant
2489 */
2490 static RPGMAKER_VERSION = "0.9.5";
2491
2492 /**
2493 * Checks whether the current RPG Maker version is greater than or equal to
2494 * the given version.
2495 *
2496 * @param {string} version - The "x.x.x" format string to compare.
2497 * @returns {boolean} True if the current version is greater than or equal
2498 * to the given version.
2499 */
2500 static checkRMVersion(version) {
2501 const array1 = this.RPGMAKER_VERSION.split("."), l = array1.length;
2502 const array2 = String(version).split(".");
2503 for (let i = 0; i < l; i++) {
2504 const [v1, v2] = [+array1[i], +array2[i]];
2505 if (v1 > v2) return true;
2506 if (v1 < v2) return false;
2507 }
2508 return true;
2509 } // checkRMVersion
2510
2511 /**
2512 * Checks whether the option is in the query string.
2513 *
2514 * @param {string} name - The option name.
2515 * @returns {boolean} True if the option is in the query string.
2516 */
2517 static isOptionValid(name) {
2518 const args = location.search.slice(1);
2519 if (args.split("&").includes(name)) return true;
2520 if (!this.isNwjs() || nw.App.argv.isEmpty()) return false;
2521 return nw.App.argv[0].split("&").includes(name);
2522 } // isOptionValid
2523
2524 /**
2525 * Checks whether the platform is NW.js.
2526 *
2527 * @returns {boolean} True if the platform is NW.js.
2528 */
2529 static isNwjs() {
2530 return typeof require === "function" && typeof process === "object";
2531 } // isNwjs
2532
2533 /**
2534 * Checks whether the platform is RPG Atsumaru.
2535 *
2536 * @returns {boolean} True if the platform is RPG Atsumaru.
2537 */
2538 static isAtsumaru() { return typeof RPGAtsumaru === "object"; }
2539
2540 /**
2541 * Checks whether the platform is a mobile device.
2542 *
2543 * @returns {boolean} True if the platform is a mobile device.
2544 */
2545 static isMobileDevice() {
2546 const r = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini/i;
2547 return !!navigator.userAgent.match(r);
2548 } // isMobileDevice
2549
2550 /**
2551 * Checks whether the browser is Mobile Safari.
2552 *
2553 * @returns {boolean} True if the browser is Mobile Safari.
2554 */
2555 static isMobileSafari() {
2556 const agent = navigator.userAgent;
2557 if (!agent.match(/iPhone|iPad|iPod/)) return false;
2558 return !!agent.match(/AppleWebKit/) && !agent.match("CriOS");
2559 } // isMobileSafari
2560
2561 /**
2562 * Checks whether the browser is Android Chrome.
2563 *
2564 * @returns {boolean} True if the browser is Android Chrome.
2565 */
2566 static isAndroidChrome() {
2567 const agent = navigator.userAgent;
2568 return !!(agent.match(/Android/) && agent.match(/Chrome/));
2569 } // isAndroidChrome
2570
2571 /**
2572 * Checks whether the browser is accessing local files.
2573 *
2574 * @returns {boolean} True if the browser is accessing local files.
2575 */
2576 static isLocal() { return window.location.href.startsWith("file:"); }
2577
2578 /**
2579 * Checks whether the browser supports WebGL.
2580 *
2581 * @returns {boolean} True if the browser supports WebGL.
2582 */
2583 static canUseWebGL() {
2584 /** @todo Thinks of if using conditional's better than try catch */
2585 try {
2586 return !!document.createElement("canvas").getContext("webgl");
2587 } catch (e) { return false; }
2588 //
2589 } // canUseWebGL
2590
2591 /**
2592 * Checks whether the browser supports Web Audio API.
2593 *
2594 * @returns {boolean} True if the browser supports Web Audio API.
2595 */
2596 static canUseWebAudioAPI() {
2597 return !!(window.AudioContext || window.webkitAudioContext);
2598 } // canUseWebAudioAPI
2599
2600 /**
2601 * Checks whether the browser supports CSS Font Loading.
2602 *
2603 * @returns {boolean} True if the browser supports CSS Font Loading.
2604 */
2605 static canUseCssFontLoadingfunction() {
2606 return !!(document.fonts && document.fonts.ready);
2607 } // canUseCssFontLoadingfunction
2608
2609 /**
2610 * Checks whether the browser supports IndexedDB.
2611 *
2612 * @returns {boolean} True if the browser supports IndexedDB.
2613 */
2614 static canUseIndexedDB() {
2615 if (!window.indexedDB) return;
2616 return !!(window.mozIndexedDB || window.webkitIndexedDB);
2617 } // canUseIndexedDB
2618
2619 /**
2620 * Checks whether the browser can play ogg files.
2621 *
2622 * @returns {boolean} True if the browser can play ogg files.
2623 */
2624 static canPlayOgg() {
2625 if (!this._audioElement) {
2626 this._audioElement = document.createElement("audio");
2627 }
2628 if (!this._audioElement) return false;
2629 return !!this._audioElement.canPlayType('audio/ogg; codecs="vorbis"');
2630 } // canPlayOgg
2631
2632 /**
2633 * Checks whether the browser can play webm files.
2634 *
2635 * @returns {boolean} True if the browser can play webm files.
2636 */
2637 static canPlayWebm() {
2638 if (!this._videoElement) {
2639 this._videoElement = document.createElement("video");
2640 }
2641 if (!this._videoElement) return false;
2642 return !!this._videoElement.canPlayType('video/webm; codecs="vp8, vorbis"');
2643 } // canPlayWebm
2644
2645 /**
2646 * Encodes a URI component without escaping slash characters.
2647 *
2648 * @param {string} str - The input string.
2649 * @returns {string} Encoded string.
2650 */
2651 static encodeURI(str) {
2652 return encodeURIComponent(str).replace(/%2F/g, "/");
2653 } // encodeURI
2654
2655 /**
2656 * Escapes special characters for HTML.
2657 *
2658 * @param {string} str - The input string.
2659 * @returns {string} Escaped string.
2660 */
2661 static escapeHtml(str) {
2662 const entityMap = {
2663 "&": "&",
2664 "<": "<",
2665 ">": ">",
2666 '"': """,
2667 "'": "'",
2668 "/": "/"
2669 };
2670 /** @todo Figures out why String(str) is needed here */
2671 return String(str).replace(/[&<>"'/]/g, s => entityMap[s]);
2672 //
2673 } // escapeHtml
2674
2675 /**
2676 * Checks whether the string contains any Arabic characters.
2677 *
2678 * @returns {boolean} True if the string contains any Arabic characters.
2679 */
2680 static containsArabic(str) {
2681 const regExp = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/;
2682 return regExp.test(str);
2683 } // containsArabic
2684
2685 /**
2686 * Sets information related to encryption.
2687 *
2688 * @param {boolean} hasImages - Whether the image files are encrypted.
2689 * @param {boolean} hasAudio - Whether the audio files are encrypted.
2690 * @param {string} key - The encryption key.
2691 */
2692 static setEncryptionInfo(hasImages, hasAudio, key) {
2693 // [Note] This function is implemented for module independence.
2694 this._hasEncryptedImages = hasImages;
2695 this._hasEncryptedAudio = hasAudio;
2696 this._encryptionKey = key;
2697 } // setEncryptionInfo
2698
2699 /**
2700 * Checks whether the image files in the game are encrypted.
2701 *
2702 * @returns {boolean} True if the image files are encrypted.
2703 */
2704 static hasEncryptedImages() { return this._hasEncryptedImages; }
2705
2706 /**
2707 * Checks whether the audio files in the game are encrypted.
2708 *
2709 * @returns {boolean} True if the audio files are encrypted.
2710 */
2711 static hasEncryptedAudio() { return this._hasEncryptedAudio; }
2712
2713 /**
2714 * Decrypts encrypted data.
2715 *
2716 * @param {ArrayBuffer} source - The data to be decrypted.
2717 * @returns {ArrayBuffer} The decrypted data.
2718 */
2719 static decryptArrayBuffer(source) {
2720 const header = new Uint8Array(source, 0, 16);
2721 const headerHex = Array.from(header, x => x.toString(16)).join(",");
2722 if (headerHex !== "52,50,47,4d,56,0,0,0,0,3,1,0,0,0,0,0") {
2723 throw new Error("Decryption error");
2724 }
2725 const [body, view] = [source.slice(16), new DataView(body)];
2726 const key = this._encryptionKey.match(/.{2}/g);
2727 for (let i = 0; i < 16; i++) {
2728 view.setUint8(i, view.getUint8(i) ^ parseInt(key[i], 16));
2729 }
2730 return body;
2731 } // decryptArrayBuffer
2732
2733} // Utils
2734
2735/*----------------------------------------------------------------------------
2736 * # Rewritten class: Video
2737 * - Rewrites it into the ES6 standard
2738 *----------------------------------------------------------------------------*/
2739
2740//-----------------------------------------------------------------------------
2741/**
2742 * The static class that handles video playback.
2743 *
2744 * @namespace
2745 */
2746class Video {
2747
2748 constructor() { throw new Error("This is a static class"); }
2749
2750 /**
2751 * Initializes the video system.
2752 *
2753 * @param {number} width - The width of the video.
2754 * @param {number} height - The height of the video.
2755 */
2756 static initialize(width, height) {
2757 [this._element, this._loading, this._volume] = [null, false, 1];
2758 this._createElement();
2759 this._setupEventHandlers();
2760 this.resize(width, height);
2761 } // initialize
2762
2763 /**
2764 * Changes the display size of the video.
2765 *
2766 * @param {number} width - The width of the video.
2767 * @param {number} height - The height of the video.
2768 */
2769 static resize(width, height) {
2770 // Edited to help plugins alter element resize behaviors in better ways
2771 if (this._element) this._resizeElem(width, height);
2772 //
2773 } // resize
2774
2775 /**
2776 * Starts playback of a video.
2777 *
2778 * @param {string} src.
2779 */
2780 static play(src) {
2781 this._element.src = src;
2782 this._element.onloadeddata = this._onLoad.bind(this);
2783 this._element.onerror = this._onError.bind(this);
2784 this._element.onended = this._onEnd.bind(this);
2785 this._element.load();
2786 this._loading = true;
2787 } // play
2788
2789 /**
2790 * Checks whether the video is playing.
2791 *
2792 * @returns {boolean} True if the video is playing.
2793 */
2794 static isPlaying() { return this._loading || this._isVisible(); }
2795
2796 /**
2797 * Sets the volume for videos.
2798 *
2799 * @param {number} volume - The volume for videos (0 to 1).
2800 */
2801 static setVolume(volume) {
2802 this._volume = volume;
2803 // Edited to help plugins alter element volume behaviors in better ways
2804 if (this._element) this._setElemVolume();
2805 //
2806 } // setVolume
2807
2808 static _createElement() {
2809 // Edited to help plugins create video element behaviors in better ways
2810 this._element = this._createdElem();
2811 //
2812 document.body.appendChild(this._element);
2813 } // _createElement
2814
2815 static _onLoad() {
2816 this._element.volume = this._volume;
2817 this._element.play();
2818 this._updateVisibility(true);
2819 this._loading = false;
2820 } // _onLoad
2821
2822 static _onError() {
2823 this._updateVisibility(false);
2824 throw ["LoadError", this._element.src, () => this._element.load()];
2825 } // _onError
2826
2827 static _onEnd() { this._updateVisibility(false); }
2828
2829 static _updateVisibility(videoVisible) {
2830 // Edited to help plugins alter visibilities updates in better ways
2831 if (videoVisible) return this._updateWhenVisible();
2832 this._updateWhenInvisible();
2833 //
2834 } // _updateVisibility
2835
2836 static _isVisible() { return this._element.style.opacity > 0; }
2837
2838 static _setupEventHandlers() {
2839 const onUserGesture = this._onUserGesture.bind(this);
2840 document.addEventListener("keydown", onUserGesture);
2841 document.addEventListener("mousedown", onUserGesture);
2842 document.addEventListener("touchend", onUserGesture);
2843 } // _setupEventHandlers
2844
2845 static _onUserGesture() {
2846 /** @todo Extracts this conditional into a well-named static function */
2847 if (this._element.src || !this._element.paused) return;
2848 //
2849 this._element.play().catch(() => 0);
2850 } // _onUserGesture
2851
2852 /**
2853 * Resizes the video element
2854 * Idempotent
2855 * @author DoubleX @since 0.9.5 @version 0.9.5
2856 */
2857 static _resizeElem(w, h) {
2858 const { style } = this._element;
2859 [style.width, style.height] = [`${w}px`, `${h}px`];
2860 } // _resizeElem
2861
2862 /**
2863 * Sets the volume of the video element
2864 * Idempotent
2865 * @author DoubleX @since 0.9.5 @version 0.9.5
2866 */
2867 static _setElemVolume() { this._element.volume = this._volume; }
2868
2869 /**
2870 * Creates a new document with id gameVideo as the video element
2871 * Nullipotent
2872 * @author DoubleX @since 0.9.5 @version 0.9.5
2873 * @returns {DOM} The video element document
2874 */
2875 static _createdElem() {
2876 const elem = document.createElement("video");
2877 elem.id = "gameVideo";
2878 const { style } = elem;
2879 [style.position, style.margin] = ["absolute", "auto"];
2880 style.top = style.left = style.right = style.bottom = style.opacity = 0;
2881 style.zIndex = 2;
2882 elem.setAttribute("playsinline", "");
2883 elem.oncontextmenu = () => false;
2884 return elem;
2885 } // _createdElem
2886
2887 /**
2888 * Updates this video when it should become visible
2889 * Idempotent
2890 * @author DoubleX @since 0.9.5 @version 0.9.5
2891 */
2892 static _updateWhenVisible() {
2893 Graphics.hideScreen();
2894 this._element.style.opacity = 1;
2895 } // _updateWhenVisible
2896
2897 /**
2898 * Updates this video when it should become invisible
2899 * Idempotent
2900 * @author DoubleX @since 0.9.5 @version 0.9.5
2901 */
2902 static _updateWhenInvisible() {
2903 Graphics.showScreen();
2904 this._element.style.opacity = 0;
2905 } // _updateWhenInvisible
2906
2907} // Video
2908
2909/*----------------------------------------------------------------------------
2910 * # Rewritten class: Bitmap
2911 * - Rewrites it into the ES6 standard
2912 *----------------------------------------------------------------------------*/
2913
2914//-----------------------------------------------------------------------------
2915/**
2916 * The basic object that represents an image.
2917 *
2918 * @class
2919 * @param {number} width - The width of the bitmap.
2920 * @param {number} height - The height of the bitmap.
2921 */
2922
2923class Bitmap {
2924
2925 constructor(width, height) {
2926 // Edited to help plugins initialize private variables in better ways
2927 this._initPrivateVars();
2928 //
2929 if (width > 0 && height > 0) this._createCanvas(width, height);
2930 // Edited to help plugins initialize public variables in better ways
2931 this._initPublicVars();
2932 //
2933 } // constructor
2934
2935 /**
2936 * Loads a image file.
2937 *
2938 * @param {string} url - The image url of the texture.
2939 * @returns {Bitmap} The new bitmap object.
2940 */
2941 load(url) {
2942 /** @todo Figures out why new Bitmap() isn't used when it's the same */
2943 const bitmap = Object.create(Bitmap.prototype);
2944 bitmap.initialize();
2945 //
2946 bitmap._url = url;
2947 bitmap._startLoading();
2948 return bitmap;
2949 } // load
2950
2951 /**
2952 * Takes a snapshot of the game screen.
2953 *
2954 * @param {Stage} stage - The stage object.
2955 * @returns {Bitmap} The new bitmap object.
2956 */
2957 snap(stage) {
2958 const [w, h] = [Graphics.width, Graphics.height];
2959 const bitmap = new Bitmap(w, h);
2960 const renderTexture = PIXI.RenderTexture.create(w, h);
2961 // Edited to help plugins alter the stage snapshot in better ways
2962 if (stage) this._snapStage(stage, renderTexture, bitmap);
2963 //
2964 renderTexture.destroy({ destroyBase: true });
2965 bitmap.baseTexture.update();
2966 return bitmap;
2967 } // snap
2968
2969 /**
2970 * Checks whether the bitmap is ready to render.
2971 *
2972 * @returns {boolean} True if the bitmap is ready to render.
2973 */
2974 isReady() {
2975 return this._loadingState === "loaded" || this._loadingState === "none";
2976 } // isReady
2977
2978 /**
2979 * Checks whether a loading error has occurred.
2980 *
2981 * @returns {boolean} True if a loading error has occurred.
2982 */
2983 isError() { return this._loadingState === "error"; }
2984
2985 /**
2986 * The url of the image file.
2987 *
2988 * @readonly
2989 * @type string
2990 * @name Bitmap#url
2991 */
2992 get url() { return this._url; }
2993
2994 /**
2995 * The base texture that holds the image.
2996 *
2997 * @readonly
2998 * @type PIXI.BaseTexture
2999 * @name Bitmap#baseTexture
3000 */
3001 get baseTexture() { return this._baseTexture; }
3002
3003 /**
3004 * The bitmap image.
3005 *
3006 * @readonly
3007 * @type HTMLImageElement
3008 * @name Bitmap#image
3009 */
3010 get image() { return this._image; }
3011
3012 /**
3013 * The bitmap canvas.
3014 *
3015 * @readonly
3016 * @type HTMLCanvasElement
3017 * @name Bitmap#canvas
3018 */
3019 get canvas() {
3020 this._ensureCanvas();
3021 return this._canvas;
3022 } // canvas
3023
3024 /**
3025 * The 2d context of the bitmap canvas.
3026 *
3027 * @readonly
3028 * @type CanvasRenderingContext2D
3029 * @name Bitmap#context
3030 */
3031 get context() {
3032 this._ensureCanvas();
3033 return this._context;
3034 } // context
3035
3036 /**
3037 * The width of the bitmap.
3038 *
3039 * @readonly
3040 * @type number
3041 * @name Bitmap#width
3042 */
3043 get width() {
3044 const image = this._canvas || this._image;
3045 return image ? image.width : 0;
3046 } // width
3047
3048 /**
3049 * The height of the bitmap.
3050 *
3051 * @readonly
3052 * @type number
3053 * @name Bitmap#height
3054 */
3055 get height() {
3056 const image = this._canvas || this._image;
3057 return image ? image.height : 0;
3058 } // height
3059
3060 /**
3061 * The rectangle of the bitmap.
3062 *
3063 * @readonly
3064 * @type Rectangle
3065 * @name Bitmap#rect
3066 */
3067 get rect() { return new Rectangle(0, 0, this.width, this.height); }
3068
3069 /**
3070 * Whether the smooth scaling is applied.
3071 *
3072 * @type boolean
3073 * @name Bitmap#smooth
3074 */
3075 get smooth() { return this._smooth; }
3076 set smooth(value) {
3077 if (this._smooth === value) return;
3078 this._smooth = value;
3079 this._updateScaleMode();
3080 } // smooth
3081
3082 /**
3083 * The opacity of the drawing object in the range (0, 255).
3084 *
3085 * @type number
3086 * @name Bitmap#paintOpacity
3087 */
3088 get paintOpacity() { return this._paintOpacity; }
3089 set paintOpacity(value) {
3090 if (this._paintOpacity === value) return;
3091 this._paintOpacity = value;
3092 this.context.globalAlpha = this._paintOpacity / 255;
3093 } // paintOpacity
3094
3095 /**
3096 * Destroys the bitmap.
3097 */
3098 destroy() {
3099 // Edited to help plugins destroy the base texture in better ways
3100 if (this._baseTexture) this._destroyBaseTexture();
3101 //
3102 this._destroyCanvas();
3103 } // destroy
3104
3105 /**
3106 * Resizes the bitmap.
3107 *
3108 * @param {number} width - The new width of the bitmap.
3109 * @param {number} height - The new height of the bitmap.
3110 */
3111 resize(width = 0, height = 0) {
3112 // They shouldn't be "", false, null, NaN or other defined falsy values
3113 const [w, h] = [Math.max(width, 1), Math.max(height, 1)];
3114 //
3115 [this.canvas.width, this.canvas.height] = [w, h];
3116 [this.baseTexture.width, this.baseTexture.height] = [w, h];
3117 } // resize
3118
3119 /**
3120 * Performs a block transfer.
3121 *
3122 * @param {Bitmap} source - The bitmap to draw.
3123 * @param {number} sx - The x coordinate in the source.
3124 * @param {number} sy - The y coordinate in the source.
3125 * @param {number} sw - The width of the source image.
3126 * @param {number} sh - The height of the source image.
3127 * @param {number} dx - The x coordinate in the destination.
3128 * @param {number} dy - The y coordinate in the destination.
3129 * @param {number} [dw=sw] The width to draw the image in the destination.
3130 * @param {number} [dh=sh] The height to draw the image in the destination.
3131 */
3132 blt(source, sx, sy, sw, sh, dx, dy, dw, dh) {
3133 /** @todo Thinks of if at least logging the catch will be better */
3134 try {
3135 // Edited to help plugins alter the blt behaviors in better ways
3136 this._bltWithoutRescue(source, sx, sy, sw, sh, dx, dy, dw, dh);
3137 //
3138 } catch (e) {}
3139 //
3140 } // blt
3141
3142 /**
3143 * Returns pixel color at the specified point.
3144 *
3145 * @param {number} x - The x coordinate of the pixel in the bitmap.
3146 * @param {number} y - The y coordinate of the pixel in the bitmap.
3147 * @returns {string} The pixel color (hex format).
3148 */
3149 getPixel(x, y) {
3150 const data = this.context.getImageData(x, y, 1, 1).data;
3151 let result = "#";
3152 for (let i = 0; i < 3; i++) result += data[i].toString(16).padZero(2);
3153 return result;
3154 } // getPixel
3155
3156 /**
3157 * Returns alpha pixel value at the specified point.
3158 *
3159 * @param {number} x - The x coordinate of the pixel in the bitmap.
3160 * @param {number} y - The y coordinate of the pixel in the bitmap.
3161 * @returns {string} The alpha value.
3162 */
3163 getAlphaPixel(x, y) {
3164 return this.context.getImageData(x, y, 1, 1).data[3];
3165 } // getAlphaPixel
3166
3167 /**
3168 * Clears the specified rectangle.
3169 *
3170 * @param {number} x - The x coordinate for the upper-left corner.
3171 * @param {number} y - The y coordinate for the upper-left corner.
3172 * @param {number} width - The width of the rectangle to clear.
3173 * @param {number} height - The height of the rectangle to clear.
3174 */
3175 clearRect(x, y, width, height) {
3176 this.context.clearRect(x, y, width, height);
3177 this._baseTexture.update();
3178 } // clearRect
3179
3180 /**
3181 * Clears the entire bitmap.
3182 */
3183 clear() { this.clearRect(0, 0, this.width, this.height); }
3184
3185 /**
3186 * Fills the specified rectangle.
3187 *
3188 * @param {number} x - The x coordinate for the upper-left corner.
3189 * @param {number} y - The y coordinate for the upper-left corner.
3190 * @param {number} width - The width of the rectangle to fill.
3191 * @param {number} height - The height of the rectangle to fill.
3192 * @param {string} color - The color of the rectangle in CSS format.
3193 */
3194 fillRect(x, y, width, height, color) {
3195 const context = this.context;
3196 context.save();
3197 context.fillStyle = color;
3198 context.fillRect(x, y, width, height);
3199 context.restore();
3200 this._baseTexture.update();
3201 } // fillRect
3202
3203 /**
3204 * Fills the entire bitmap.
3205 *
3206 * @param {string} color - The color of the rectangle in CSS format.
3207 */
3208 fillAll(color) { this.fillRect(0, 0, this.width, this.height, color); }
3209
3210 /**
3211 * Draws the specified rectangular frame.
3212 *
3213 * @param {number} x - The x coordinate for the upper-left corner.
3214 * @param {number} y - The y coordinate for the upper-left corner.
3215 * @param {number} width - The width of the rectangle to fill.
3216 * @param {number} height - The height of the rectangle to fill.
3217 * @param {string} color - The color of the rectangle in CSS format.
3218 */
3219 strokeRect(x, y, width, height, color) {
3220 const context = this.context;
3221 context.save();
3222 context.strokeStyle = color;
3223 context.strokeRect(x, y, width, height);
3224 context.restore();
3225 this._baseTexture.update();
3226 } // strokeRect
3227
3228 // prettier-ignore
3229 /**
3230 * Draws the rectangle with a gradation.
3231 *
3232 * @param {number} x - The x coordinate for the upper-left corner.
3233 * @param {number} y - The y coordinate for the upper-left corner.
3234 * @param {number} width - The width of the rectangle to fill.
3235 * @param {number} height - The height of the rectangle to fill.
3236 * @param {string} color1 - The gradient starting color.
3237 * @param {string} color2 - The gradient ending color.
3238 * @param {boolean} vertical - Whether the gradient should be draw as vertical or not.
3239 */
3240 gradientFillRect(x, y, width, height, color1, color2, vertical) {
3241 const context = this.context;
3242 const [x1, y1] = [vertical ? x : x + width, vertical ? y + height : y];
3243 const grad = context.createLinearGradient(x, y, x1, y1);
3244 grad.addColorStop(0, color1);
3245 grad.addColorStop(1, color2);
3246 context.save();
3247 context.fillStyle = grad;
3248 context.fillRect(x, y, width, height);
3249 context.restore();
3250 this._baseTexture.update();
3251 } // gradientFillRect
3252
3253 /**
3254 * Draws a bitmap in the shape of a circle.
3255 *
3256 * @param {number} x - The x coordinate based on the circle center.
3257 * @param {number} y - The y coordinate based on the circle center.
3258 * @param {number} radius - The radius of the circle.
3259 * @param {string} color - The color of the circle in CSS format.
3260 */
3261 drawCircle(x, y, radius, color) {
3262 const context = this.context;
3263 context.save();
3264 context.fillStyle = color;
3265 context.beginPath();
3266 context.arc(x, y, radius, 0, Math.PI * 2, false);
3267 context.fill();
3268 context.restore();
3269 this._baseTexture.update();
3270 } // drawCircle
3271
3272 /**
3273 * Draws the outline text to the bitmap.
3274 *
3275 * @param {string} text - The text that will be drawn.
3276 * @param {number} x - The x coordinate for the left of the text.
3277 * @param {number} y - The y coordinate for the top of the text.
3278 * @param {number} maxWidth - The maximum allowed width of the text.
3279 * @param {number} lineHeight - The height of the text line.
3280 * @param {string} align - The alignment of the text.
3281 */
3282 drawText(text, x, y, maxWidth, lineHeight, align) {
3283 // [Note] Different browser makes different rendering with
3284 // textBaseline == 'top'. So we use 'alphabetic' here.
3285 const { context, context: { alpha } } = this;
3286 maxWidth = maxWidth || 0xffffffff;
3287 // Edited to help plugins alter the text align behaviors in better ways
3288 const tx = this._drawnTextX(align, x, maxWidth);
3289 //
3290 // Edited to help plugins alter the line height behaviors in better ways
3291 const ty = this._drawnTextY(y, lineHeight);
3292 //
3293 context.save();
3294 [context.font, context.textAlign] = [this._makeFontNameText(), align];
3295 [context.textBaseline, context.globalAlpha] = ["alphabetic", 1];
3296 this._drawTextOutline(text, tx, ty, maxWidth);
3297 context.globalAlpha = alpha;
3298 this._drawTextBody(text, tx, ty, maxWidth);
3299 context.restore();
3300 this._baseTexture.update();
3301 } // drawText
3302
3303 /**
3304 * Returns the width of the specified text.
3305 *
3306 * @param {string} text - The text to be measured.
3307 * @returns {number} The width of the text in pixels.
3308 */
3309 measureTextWidth(text) {
3310 const context = this.context;
3311 context.save();
3312 context.font = this._makeFontNameText();
3313 const width = context.measureText(text).width;
3314 context.restore();
3315 return width;
3316 } // measureTextWidth
3317
3318 /**
3319 * Adds a callback function that will be called when the bitmap is loaded.
3320 *
3321 * @param {function} listner - The callback function.
3322 */
3323 addLoadListener(listner) {
3324 this.isReady() ? listner(this) : this._loadListeners.push(listner);
3325 } // addLoadListener
3326
3327 /**
3328 * Tries to load the image again.
3329 */
3330 retry() { this._startLoading(); }
3331
3332 _makeFontNameText() {
3333 const italic = this.fontItalic ? "Italic " : "";
3334 const bold = this.fontBold ? "Bold " : "";
3335 return `${italic}${bold}${this.fontSize}px ${this.fontFace}`;
3336 } // _makeFontNameText
3337
3338 _drawTextOutline(text, tx, ty, maxWidth) {
3339 const context = this.context;
3340 context.strokeStyle = this.outlineColor;
3341 [context.lineWidth, context.lineJoin] = [this.outlineWidth, "round"];
3342 context.strokeText(text, tx, ty, maxWidth);
3343 } // _drawTextOutline
3344
3345 _drawTextBody(text, tx, ty, maxWidth) {
3346 const context = this.context;
3347 context.fillStyle = this.textColor;
3348 context.fillText(text, tx, ty, maxWidth);
3349 } // _drawTextBody
3350
3351 _createCanvas(width, height) {
3352 this._canvas = document.createElement("canvas");
3353 this._context = this._canvas.getContext("2d");
3354 [this._canvas.width, this._canvas.height] = [width, height];
3355 this._createBaseTexture(this._canvas);
3356 } // _createCanvas
3357
3358 _ensureCanvas() {
3359 if (this._canvas) return;
3360 if (this._image) return this._ensureCanvasWithImage();
3361 this._createCanvas(0, 0);
3362 } // _ensureCanvas
3363
3364 _destroyCanvas() {
3365 // Edited to help plugins alter the canvas destructions in better ways
3366 if (this._canvas) this._destroyExistingCanvas();
3367 //
3368 } // _destroyCanvas
3369
3370 _createBaseTexture(source) {
3371 // Edited to help plugins alter the base texture behaviors in better way
3372 this._baseTexture = this._newBaseTexture(source);
3373 //
3374 this._updateScaleModeWithBaseTexture();
3375 } // _createBaseTexture
3376
3377 _updateScaleMode() {
3378 // Edited to help plugins alter the scale mode behaviors in better ways
3379 if (this._baseTexture) this._updateScaleModeWithBaseTexture();
3380 //
3381 } // _updateScaleMode
3382
3383 _startLoading() {
3384 // Edited to help plugins alter the image behaviors in better ways
3385 this._image = this._newImage();
3386 //
3387 this._destroyCanvas();
3388 this._loadingState = "loading";
3389 if (Utils.hasEncryptedImages()) return this._startDecrypting();
3390 this._image.src = this._url;
3391 } // _startLoading
3392
3393 // Edited to help pluggins alter the xhr behaviors in better ways
3394 _startDecrypting() { this._newDecryptingXhr().send(); }
3395 //
3396
3397 _onXhrLoad(xhr) {
3398 // Edited to help pluggins alter the xhr load behaviors in better ways
3399 xhr.status < 400 ? this._onXhrLoadSuc(xhr) : this._onError();
3400 //
3401 } // _onXhrLoad
3402
3403 _onLoad() {
3404 if (Utils.hasEncryptedImages()) URL.revokeObjectURL(this._image.src);
3405 this._loadingState = "loaded";
3406 this._createBaseTexture(this._image);
3407 this._callLoadListeners();
3408 } // _onLoad
3409
3410 _callLoadListeners() {
3411 const loadListeners = this._loadListeners;
3412 while (!loadListeners.isEmpty()) loadListeners.shift()(this);
3413 } // _callLoadListeners
3414
3415 _onError() { this._loadingState = "error"; }
3416
3417 /**
3418 * Initializes all private variables of this bitmap
3419 * Idempotent
3420 * @author DoubleX @since 0.9.5 @version 0.9.5
3421 */
3422 _initPrivateVars() {
3423 this._canvas = this._context = this._baseTexture = this._image = null;
3424 [this._url, this._paintOpacity] = ["", 255];
3425 [this._smooth, this._loadListeners] = [true, []];
3426 // "none", "loading", "loaded", or "error"
3427 this._loadingState = "none";
3428 } // _initPrivateVars
3429
3430 /**
3431 * Initializes all public variables of this bitmap
3432 * Idempotent
3433 * @author DoubleX @since 0.9.5 @version 0.9.5
3434 */
3435 _initPublicVars() {
3436 /**
3437 * The face name of the font.
3438 *
3439 * @type string
3440 */
3441 this.fontFace = "sans-serif";
3442 /**
3443 * The size of the font in pixels.
3444 *
3445 * @type number
3446 */
3447 this.fontSize = 16;
3448 /**
3449 * Whether the font is bold.
3450 *
3451 * @type boolean
3452 */
3453 this.fontBold = false;
3454 /**
3455 * Whether the font is italic.
3456 *
3457 * @type boolean
3458 */
3459 this.fontItalic = false;
3460 /**
3461 * The color of the text in CSS format.
3462 *
3463 * @type string
3464 */
3465 this.textColor = "#ffffff";
3466 /**
3467 * The color of the outline of the text in CSS format.
3468 *
3469 * @type string
3470 */
3471 this.outlineColor = "rgba(0, 0, 0, 0.5)";
3472 /**
3473 * The width of the outline of the text.
3474 *
3475 * @type number
3476 */
3477 this.outlineWidth = 3;
3478 } // _initPublicVars
3479
3480 /**
3481 * Use this bitmap to take the snapshot of the current game stage
3482 * Idempotent
3483 * @author DoubleX @since 0.9.5 @version 0.9.5
3484 */
3485 _snapStage(stage, renderTexture, bitmap) {
3486 const renderer = Graphics.app.renderer;
3487 renderer.render(stage, renderTexture);
3488 stage.worldTransform.identity();
3489 bitmap.context.drawImage(renderer.extract.canvas(renderTexture), 0, 0);
3490 } // _snapStage
3491
3492 /**
3493 * This method shouldn't be called without an existing base texture
3494 * Idempotent
3495 * @author DoubleX @since 0.9.5 @version 0.9.5
3496 */
3497 _destroyBaseTexture() {
3498 this._baseTexture.destroy();
3499 this._baseTexture = null;
3500 } // _destroyBaseTexture
3501
3502 /**
3503 * This method shouldn't be used without a try-catch
3504 * Potential Hotspot/Idempotent
3505 * @author DoubleX @since 0.9.5 @version 0.9.5
3506 * @param {Bitmap} source - The bitmap to draw.
3507 * @param {number} sx - The x coordinate in the source
3508 * @param {number} sy - The y coordinate in the source
3509 * @param {number} sw - The width of the source image
3510 * @param {number} sh - The height of the source image
3511 * @param {number} dx - The x coordinate in the destination
3512 * @param {number} dy - The y coordinate in the destination
3513 * @param {number} [dw=sw] The width to draw the image in the destination
3514 * @param {number} [dh=sh] The height to draw the image in the destination
3515 */
3516 _bltWithoutRescue(source, sx, sy, sw, sh, dx, dy, dw, dh) {
3517 // Default parameters can't be used as it's possible for them to be 0
3518 [dw, dh] = [dw || sw, dh || sh];
3519 //
3520 const image = source._canvas || source._image;
3521 this.context.globalCompositeOperation = "source-over";
3522 this.context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
3523 this._baseTexture.update();
3524 } // _bltWithoutRescue
3525
3526 /**
3527 * Potential Hotspot/Nullipotent
3528 * @author DoubleX @since 0.9.5 @version 0.9.5
3529 * @param {number} x - The x coordinate for the top of the text
3530 * @param {number} lineHeight - The height of the text line
3531 * @returns {number} The x position of the text to be drawn
3532 */
3533 _drawnTextX(align, x, maxWidth) {
3534 if (align === "center") return x + maxWidth / 2;
3535 if (align === "right") return x + maxWidth;
3536 return x;
3537 } // _drawnTextX
3538
3539 /**
3540 * Potential Hotspot/Nullipotent
3541 * @author DoubleX @since 0.9.5 @version 0.9.5
3542 * @param {number} y - The y coordinate for the top of the text
3543 * @param {number} lineHeight - The height of the text line
3544 * @returns {number} The y position of the text to be drawn
3545 */
3546 _drawnTextY(y, lineHeight) {
3547 return Math.round(y + lineHeight / 2 + this.fontSize * 0.35);
3548 } // _drawnTextY
3549
3550 /**
3551 * This method shouldn't be called without an existing image
3552 * Idempotent
3553 * @author DoubleX @since 0.9.5 @version 0.9.5
3554 */
3555 _ensureCanvasWithImage() {
3556 this._createCanvas(this._image.width, this._image.height);
3557 this._context.drawImage(this._image, 0, 0);
3558 } // _ensureCanvasWithImage
3559
3560 /**
3561 * This method shouldn't be called without an existing canvas
3562 * Idempotent
3563 * @author DoubleX @since 0.9.5 @version 0.9.5
3564 */
3565 _destroyExistingCanvas() {
3566 this._canvas.width = this._canvas.height = 0;
3567 this._canvas = null;
3568 } // _destroyExistingCanvas
3569
3570 /**
3571 * Nullipotent
3572 * @author DoubleX @since 0.9.5 @version 0.9.5
3573 * @returns {PIXI.BaseTexture} The new base texture of this bitmap
3574 */
3575 _newBaseTexture(source) {
3576 const baseTexture = new PIXI.BaseTexture(source);
3577 baseTexture.mipmap = false;
3578 [baseTexture.width, baseTexture.height] = [source.width, source.height];
3579 return baseTexture;
3580 } // _newBaseTexture
3581
3582 /**
3583 * This method shouldn't be called without an existing base texture
3584 * Idempotent
3585 * @author DoubleX @since 0.9.5 @version 0.9.5
3586 */
3587 _updateScaleModeWithBaseTexture() {
3588 if (this._smooth) {
3589 this._baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
3590 } else this._baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
3591 } // _updateScaleModeWithBaseTexture
3592
3593 /**
3594 * Nullipotent
3595 * @author DoubleX @since 0.9.5 @version 0.9.5
3596 * @returns {Image} The new image of this bitmap
3597 */
3598 _newImage() {
3599 const image = new Image();
3600 image.onload = this._onLoad.bind(this);
3601 image.onerror = this._onError.bind(this);
3602 return image;
3603 } // _newImage
3604
3605 /**
3606 * Nullipotent
3607 * @author DoubleX @since 0.9.5 @version 0.9.5
3608 * @returns {XMLHttpRequest} The GET XMLHttpRequest receiving array buffers
3609 */
3610 _newDecryptingXhr() {
3611 const xhr = new XMLHttpRequest();
3612 xhr.open("GET", `${this._url}_`);
3613 xhr.responseType = "arraybuffer";
3614 xhr.onload = this._onXhrLoad.bind(this, xhr);
3615 xhr.onerror = this._onError.bind(this);
3616 return xhr;
3617 } // _newDecryptingXhr
3618
3619 /**
3620 * Sets the image source by the XMLHttpRequeset response
3621 * Idempotent
3622 * @author DoubleX @since 0.9.5 @version 0.9.5
3623 * @param {XMLHttpRequest} xhr - The GET XMLHttpRequest with array buffers
3624 */
3625 _onXhrLoadSuc(xhr) {
3626 const arrayBuffer = Utils.decryptArrayBuffer(xhr.response);
3627 this._image.src = URL.createObjectURL(new Blob([arrayBuffer]));
3628 } // _onXhrLoadSuc
3629
3630} // Bitmap
3631
3632/*----------------------------------------------------------------------------
3633 * # Rewritten class: ColorFilter
3634 * - Rewrites it into the ES6 standard
3635 *----------------------------------------------------------------------------*/
3636
3637//-----------------------------------------------------------------------------
3638/**
3639 * The color filter for WebGL.
3640 *
3641 * @class
3642 * @extends PIXI.Filter
3643 */
3644
3645class ColorFilter extends PIXI.Filter {
3646
3647 constructor() {
3648 super(null, this._fragmentSrc());
3649 // Edited to help plugins setup the color filter uniforms in better ways
3650 this._initUniforms();
3651 //
3652 } // constructor
3653
3654 /**
3655 * Sets the hue rotation value.
3656 *
3657 * @param {number} hue - The hue value (-360, 360).
3658 */
3659 setHue(hue) { this.uniforms.hue = +hue; }
3660
3661 /**
3662 * Sets the color tone.
3663 *
3664 * @param {array} tone - The color tone [r, g, b, gray].
3665 */
3666 setColorTone(tone) {
3667 if (!(tone instanceof Array)) {
3668 throw new Error("Argument must be an array");
3669 }
3670 this.uniforms.colorTone = tone.clone();
3671 } // setColorTone
3672
3673 /**
3674 * Sets the blend color.
3675 *
3676 * @param {array} color - The blend color [r, g, b, a].
3677 */
3678 setBlendColor(color) {
3679 if (!(color instanceof Array)) {
3680 throw new Error("Argument must be an array");
3681 }
3682 this.uniforms.blendColor = color.clone();
3683 } // setBlendColor
3684
3685 /**
3686 * Sets the brightness.
3687 *
3688 * @param {number} brightness - The brightness (0 to 255).
3689 */
3690 setBrightness(brightness) { this.uniforms.brightness = +brightness; }
3691
3692 _fragmentSrc() {
3693 return `varying vec2 vTextureCoord;
3694 uniform sampler2D uSampler;
3695 uniform float hue;
3696 uniform vec4 colorTone;
3697 uniform vec4 blendColor;
3698 uniform float brightness;
3699 vec3 rgbToHsl(vec3 rgb) {
3700 float r = rgb.r;
3701 float g = rgb.g;
3702 float b = rgb.b;
3703 float cmin = min(r, min(g, b));
3704 float cmax = max(r, max(g, b));
3705 float h = 0.0;
3706 float s = 0.0;
3707 float l = (cmin + cmax) / 2.0;
3708 float delta = cmax - cmin;
3709 if (delta > 0.0) {
3710 if (r == cmax) {
3711 h = mod((g - b) / delta + 6.0, 6.0) / 6.0;
3712 } else if (g == cmax) {
3713 h = ((b - r) / delta + 2.0) / 6.0;
3714 else {
3715 h = ((r - g) / delta + 4.0) / 6.0;
3716 }
3717 if (l < 1.0) {
3718 s = delta / (1.0 - abs(2.0 * l - 1.0));
3719 }
3720 }
3721 return vec3(h, s, l);
3722 }
3723 vec3 hslToRgb(vec3 hsl) {
3724 float h = hsl.x;
3725 float l = hsl.z;
3726 float c = (1.0 - abs(2.0 * l - 1.0)) * hsl.y;
3727 float m = l - c / 2.0;
3728 float cm = c + m;
3729 float xm = c * (1.0 - abs((mod(h * 6.0, 2.0)) - 1.0)) + m;
3730 if (h < 1.0 / 6.0) {
3731 return vec3(cm, xm, m);
3732 } else if (h < 2.0 / 6.0) {
3733 return vec3(xm, cm, m);
3734 } else if (h < 3.0 / 6.0) {
3735 return vec3(m, cm, xm);
3736 } else if (h < 4.0 / 6.0) {
3737 return vec3(m, xm, cm);
3738 } else if (h < 5.0 / 6.0) {
3739 return vec3(xm, m, cm);
3740 } else {
3741 return vec3(cm, m, xm);
3742 }
3743 }
3744 float fragColorR(float r, float a, float i1, float i3) {
3745 r = clamp((r / a + colorTone.r / 255.0) * a, 0.0, 1.0);
3746 r = clamp(r * i1 + blendColor.r / 255.0 * i3 * a, 0.0, 1.0);
3747 return r * brightness / 255.0;
3748 }
3749 float fragColorG(float g, float a, float i1, float i3) {
3750 g = clamp((g / a + colorTone.g / 255.0) * a, 0.0, 1.0);
3751 g = clamp(g * i1 + blendColor.g / 255.0 * i3 * a, 0.0, 1.0);
3752 return g * brightness / 255.0;
3753 }
3754 float fragColorB(float b, float a, float i1, float i3) {
3755 b = clamp((b / a + colorTone.b / 255.0) * a, 0.0, 1.0);
3756 b = clamp(b * i1 + blendColor.b / 255.0 * i3 * a, 0.0, 1.0);
3757 return b * brightness / 255.0;
3758 }
3759 void main() {
3760 vec4 sample = texture2D(uSampler, vTextureCoord);
3761 float a = sample.a;
3762 vec3 hsl = rgbToHsl(sample.rgb);
3763 hsl.x = mod(hsl.x + hue / 360.0, 1.0);
3764 hsl.y = hsl.y * (1.0 - colorTone.a / 255.0);
3765 vec3 rgb = hslToRgb(hsl);
3766 float i3 = blendColor.a / 255.0;
3767 float i1 = 1.0 - i3;
3768 float r = fragColorR(rgb.r, a, i1, i3);
3769 float g = fragColorG(rgb.g, a, i1, i3);
3770 float b = fragColorB(rgb.b, a, i1, i3);
3771 gl_FragColor = vec4(r, g, b, a);
3772 }`;
3773 } // _fragmentSrc
3774
3775 /**
3776 * Initializes the uniforms of this color filter
3777 * Idempotent
3778 * @author DoubleX @since 0.9.5 @version 0.9.5
3779 */
3780 _initUniforms() {
3781 this.uniforms.hue = 0;
3782 this.uniforms.colorTone = [0, 0, 0, 0];
3783 this.uniforms.blendColor = [0, 0, 0, 0];
3784 this.uniforms.brightness = 255;
3785 } // _initUniforms
3786
3787} // ColorFilter
3788// It's just to play safe in case of any plugin extending PIXI.Filter in ES6 way
3789ES6ExtendedClassAlias.inherit(ColorFilter);
3790//
3791
3792/*----------------------------------------------------------------------------
3793 * # Rewritten class: Point
3794 * - Rewrites it into the ES6 standard
3795 *----------------------------------------------------------------------------*/
3796
3797//-----------------------------------------------------------------------------
3798/**
3799 * The point class.
3800 *
3801 * @class
3802 * @extends PIXI.Point
3803 * @param {number} x - The x coordinate.
3804 * @param {number} y - The y coordinate.
3805 */
3806class Point extends PIXI.Point {}
3807// It's just to play safe in case of any plugin extending PIXI.Point in ES6 way
3808ES6ExtendedClassAlias.inherit(Point);
3809//
3810
3811/*----------------------------------------------------------------------------
3812 * # Rewritten class: Rectangle
3813 * - Rewrites it into the ES6 standard
3814 *----------------------------------------------------------------------------*/
3815
3816//-----------------------------------------------------------------------------
3817/**
3818 * The rectangle class.
3819 *
3820 * @class
3821 * @extends PIXI.Rectangle
3822 * @param {number} x - The x coordinate for the upper-left corner.
3823 * @param {number} y - The y coordinate for the upper-left corner.
3824 * @param {number} width - The width of the rectangle.
3825 * @param {number} height - The height of the rectangle.
3826 */
3827class Rectangle extends PIXI.Rectangle {}
3828// It's just to play safe in case of any plugin extending PIXI.Rectangle in ES6
3829ES6ExtendedClassAlias.inherit(Rectangle);
3830//
3831
3832/*----------------------------------------------------------------------------
3833 * # Rewritten class: ScreenSprite
3834 * - Rewrites it into the ES6 standard
3835 *----------------------------------------------------------------------------*/
3836
3837//-----------------------------------------------------------------------------
3838/**
3839 * The sprite which covers the entire game screen.
3840 *
3841 * @class
3842 * @extends PIXI.Container
3843 */
3844class ScreenSprite extends PIXI.Container {
3845
3846 constructor() {
3847 super();
3848 this._graphics = new PIXI.Graphics();
3849 this.addChild(this._graphics);
3850 this.opacity = 0;
3851 this._red = this._green = this._blue = -1;
3852 this.setBlack();
3853 } // constructor
3854
3855 /**
3856 * The opacity of the sprite (0 to 255).
3857 *
3858 * @type number
3859 * @name ScreenSprite#opacity
3860 */
3861 get opacity() { return this.alpha * 255; }
3862 set opacity(value) { this.alpha = value.clamp(0, 255) / 255; }
3863
3864 /**
3865 * Destroys the screen sprite.
3866 */
3867 destroy() { super.destroy({ children: true, texture: true }); }
3868
3869 /**
3870 * Sets black to the color of the screen sprite.
3871 */
3872 setBlack() { this.setColor(0, 0, 0); }
3873
3874 /**
3875 * Sets white to the color of the screen sprite.
3876 */
3877 setWhite() { this.setColor(255, 255, 255); }
3878
3879 /**
3880 * Sets the color of the screen sprite by values.
3881 *
3882 * @param {number} r - The red value in the range (0, 255).
3883 * @param {number} g - The green value in the range (0, 255).
3884 * @param {number} b - The blue value in the range (0, 255).
3885 */
3886 setColor(r, g, b) {
3887 // Edited to help plugins alter the set color behaviors in better ways
3888 if (this._isNewColor(r, g, b)) this._setNewColor(r, g, b);
3889 //
3890 } // setColor
3891
3892 /**
3893 * Nullipotent
3894 * @author DoubleX @since 0.9.5 @version 0.9.5
3895 * @param {number} r - The red value in the range (0, 255)
3896 * @param {number} g - The green value in the range (0, 255)
3897 * @param {number} b - The blue value in the range (0, 255)
3898 * @returns {boolean} If the specified color's different from the current
3899 */
3900 _isNewColor(r, g, b) {
3901 return this._red !== r || this._green !== g || this._blue !== b;
3902 } // _isNewColor
3903
3904 /**
3905 * Sets the new screen sprite red, green and blue color components
3906 * Idempotent
3907 * @author DoubleX @since 0.9.5 @version 0.9.5
3908 * @param {number} r - The red value in the range (0, 255)
3909 * @param {number} g - The green value in the range (0, 255)
3910 * @param {number} b - The blue value in the range (0, 255)
3911 */
3912 _setNewColor(r = 0, g = 0, b = 0) {
3913 // They shouldn't be "", false, null, NaN or other defined falsy values
3914 [this._red, this._green, this._blue] = [r, g, b].map(component => {
3915 return Math.round(component).clamp(0, 255);
3916 });
3917 //
3918 const graphics = this._graphics;
3919 graphics.clear();
3920 graphics.beginFill((this._red << 16) | (this._green << 8) | this._blue, 1);
3921 /** @todo Fugures out where do -50000 and 100000 come from */
3922 graphics.drawRect(-50000, -50000, 100000, 100000);
3923 //
3924 } // _setNewColor
3925
3926} // ScreenSprite
3927// It's just to play safe in case of any plugin extending PIXI.Container in ES6
3928ES6ExtendedClassAlias.inherit(ScreenSprite);
3929//
3930
3931/*----------------------------------------------------------------------------
3932 * # Rewritten class: Sprite
3933 * - Rewrites it into the ES6 standard
3934 *----------------------------------------------------------------------------*/
3935
3936//-----------------------------------------------------------------------------
3937/**
3938 * The basic object that is rendered to the game screen.
3939 *
3940 * @class
3941 * @extends PIXI.Sprite
3942 * @param {Bitmap} bitmap - The image for the sprite.
3943 */
3944class Sprite extends PIXI.Sprite {
3945
3946 constructor(bitmap) {
3947 // Edited to help plugins setups the empty base texture in better ways
3948 Sprite._initEmptyBaseTexture();
3949 //
3950 const frame = new Rectangle();
3951 const texture = new PIXI.Texture(Sprite._emptyBaseTexture, frame);
3952 super(texture);
3953 [this.spriteId, this._bitmap] = [Sprite._counter++, bitmap];
3954 [this._frame, this._hue] = [frame, 0];
3955 [this._blendColor, this._colorTone] = [[0, 0, 0, 0], [0, 0, 0, 0]];
3956 [this._colorFilter, this._blendMode] = [null, PIXI.BLEND_MODES.NORMAL];
3957 this._hidden = false;
3958 this._onBitmapChange();
3959 } // constructor
3960
3961 static _emptyBaseTexture = null;
3962 static _counter = 0;
3963
3964 /**
3965 * The image for the sprite.
3966 *
3967 * @type Bitmap
3968 * @name Sprite#bitmap
3969 */
3970 get bitmap() { return this._bitmap; }
3971 set bitmap(value) {
3972 if (this._bitmap === value) return;
3973 this._bitmap = value;
3974 this._onBitmapChange();
3975 } // bitmap
3976
3977 /**
3978 * The width of the sprite without the scale.
3979 *
3980 * @type number
3981 * @name Sprite#width
3982 */
3983 get width() { return this._frame.width; }
3984 set width(value) {
3985 /** @todo Checks if these should be called if the width's unchanged */
3986 this._frame.width = value;
3987 this._refresh();
3988 //
3989 } // width
3990
3991 /**
3992 * The height of the sprite without the scale.
3993 *
3994 * @type number
3995 * @name Sprite#height
3996 */
3997 get height() { return this._frame.height; }
3998 set height(value) {
3999 /** @todo Checks if these should be called if the height's unchanged */
4000 this._frame.height = value;
4001 this._refresh();
4002 //
4003 } // height
4004
4005 /**
4006 * The opacity of the sprite (0 to 255).
4007 *
4008 * @type number
4009 * @name Sprite#opacity
4010 */
4011 get opacity() { return this.alpha * 255; }
4012 set opacity(value) { this.alpha = value.clamp(0, 255) / 255; }
4013
4014 /**
4015 * The blend mode to be applied to the sprite.
4016 *
4017 * @type number
4018 * @name Sprite#blendMode
4019 */
4020 get blendMode() {
4021 if (this._colorFilter) return this._colorFilter.blendMode;
4022 return this._blendMode;
4023 } // blendMode
4024 set blendMode(value) {
4025 this._blendMode = value;
4026 if (this._colorFilter) this._colorFilter.blendMode = value;
4027 } // blendMode
4028
4029 /**
4030 * Destroys the sprite.
4031 */
4032 destroy() { super.destroy({ children: true, texture: true }); }
4033
4034 /**
4035 * Updates the sprite for each frame.
4036 */
4037 update() {
4038 /** @todo Checks if an invisible sprite needs to be updated */
4039 this.children.forEach(child => { if (child.update) child.update(); });
4040 //
4041 } // update
4042
4043 /**
4044 * Makes the sprite "hidden".
4045 */
4046 hide() {
4047 this._hidden = true;
4048 this.updateVisibility();
4049 } // hide
4050
4051 /**
4052 * Releases the "hidden" state of the sprite.
4053 */
4054 show() {
4055 this._hidden = false;
4056 this.updateVisibility();
4057 } // show
4058
4059 /**
4060 * Reflects the "hidden" state of the sprite to the visible state.
4061 */
4062 updateVisibility() { this.visible = !this._hidden; }
4063
4064 /**
4065 * Sets the x and y at once.
4066 *
4067 * @param {number} x - The x coordinate of the sprite.
4068 * @param {number} y - The y coordinate of the sprite.
4069 */
4070 move(x, y) { [this.x, this.y] = [x, y]; }
4071
4072 /**
4073 * Sets the rectagle of the bitmap that the sprite displays.
4074 *
4075 * @param {number} x - The x coordinate of the frame.
4076 * @param {number} y - The y coordinate of the frame.
4077 * @param {number} width - The width of the frame.
4078 * @param {number} height - The height of the frame.
4079 */
4080 setFrame(x, y, width, height) {
4081 this._refreshFrame = false;
4082 // Edited to help plugins alter the set frame behaviors in better ways
4083 if (!this._isNewFrame(x, y, width, height)) return;
4084 this._setNewFrame(x, y, width, height);
4085 //
4086 } // setFrame
4087
4088 /**
4089 * Sets the hue rotation value.
4090 *
4091 * @param {number} hue - The hue value (-360, 360).
4092 */
4093 setHue(hue) {
4094 const hueNum = +hue;
4095 if (this._hue === hueNum) return;
4096 this._hue = hueNum;
4097 this._updateColorFilter();
4098 } // setHue
4099
4100 /**
4101 * Gets the blend color for the sprite.
4102 *
4103 * @returns {array} The blend color [r, g, b, a].
4104 */
4105 getBlendColor() { return this._blendColor.clone(); }
4106
4107 /**
4108 * Sets the blend color for the sprite.
4109 *
4110 * @param {array} color - The blend color [r, g, b, a].
4111 */
4112 setBlendColor(color) {
4113 if (!(color instanceof Array)) {
4114 throw new Error("Argument must be an array");
4115 }
4116 if (this._blendColor.equals(color)) return;
4117 this._blendColor = color.clone();
4118 this._updateColorFilter();
4119 } // setBlendColor
4120
4121 /**
4122 * Gets the color tone for the sprite.
4123 *
4124 * @returns {array} The color tone [r, g, b, gray].
4125 */
4126 getColorTone() { return this._colorTone.clone(); }
4127
4128 /**
4129 * Sets the color tone for the sprite.
4130 *
4131 * @param {array} tone - The color tone [r, g, b, gray].
4132 */
4133 setColorTone(tone) {
4134 if (!(tone instanceof Array)) {
4135 throw new Error("Argument must be an array");
4136 }
4137 if (this._colorTone.equals(tone)) return;
4138 this._colorTone = tone.clone();
4139 this._updateColorFilter();
4140 } // setColorTone
4141
4142 _onBitmapChange() {
4143 // Edited to help plugins alter bitmap change behaviors in better ways
4144 if (this._bitmap) return this._onExistingBitmapChange();
4145 this._onNullBitmapChange();
4146 //
4147 } // _onBitmapChange
4148
4149 _onBitmapLoad(bitmapLoaded) {
4150 // Edited to help plugins alter bitmap loading behaviors in better ways
4151 if (bitmapLoaded === this._bitmap) this._onThisBitmapLoaded();
4152 //
4153 this._refresh();
4154 } // _onBitmapLoad
4155
4156 // Edited to help plugins alter texture refresh behaviors in better ways
4157 _refresh() { if (this.texture) this._refreshWithTexture(); }
4158 //
4159
4160 _createColorFilter() {
4161 this._colorFilter = new ColorFilter();
4162 this.filters = this.filters || [];
4163 this.filters.push(this._colorFilter);
4164 } // _createColorFilter
4165
4166 _updateColorFilter() {
4167 if (!this._colorFilter) this._createColorFilter();
4168 this._colorFilter.setHue(this._hue);
4169 this._colorFilter.setBlendColor(this._blendColor);
4170 this._colorFilter.setColorTone(this._colorTone);
4171 } // _updateColorFilter
4172
4173 /**
4174 * Initializes the new static empty base texture of the Sprite class
4175 * Idempotent
4176 * @author DoubleX @since 0.9.5 @version 0.9.5
4177 */
4178 static _initEmptyBaseTexture() {
4179 if (this._emptyBaseTexture) return;
4180 this._emptyBaseTexture = this._newEmptyBaseTexture();
4181 } // _initEmptyBaseTexture
4182
4183 /**
4184 * Nullipotent
4185 * @author DoubleX @since 0.9.5 @version 0.9.5
4186 * @returns {PIXI.BaseTexture} The new static empty base texture of Sprite
4187 */
4188 static _newEmptyBaseTexture() {
4189 const emptyBaseTexture = new PIXI.BaseTexture();
4190 emptyBaseTexture.setSize(1, 1);
4191 return emptyBaseTexture;
4192 } // _newEmptyBaseTexture
4193
4194 /**
4195 * Nullipotent
4196 * @author DoubleX @since 0.9.5 @version 0.9.5
4197 * @param {number} x - The x coordinate of the frame
4198 * @param {number} y - The y coordinate of the frame
4199 * @param {number} width - The width of the frame
4200 * @param {number} height - The height of the frame
4201 * @returns {boolean} If it's indeed a new sprite bitmap rectangle
4202 */
4203 _isNewFrame(x, y, width, height) {
4204 const frame = this._frame;
4205 if (x !== frame.x || y !== frame.y) return true;
4206 return width !== frame.width || height !== frame.height;
4207 } // _isNewFrame
4208
4209 /**
4210 * Sets the new rectangle of the bitmap that this sprite displays
4211 * Idempotent
4212 * @author DoubleX @since 0.9.5 @version 0.9.5
4213 * @param {number} x - The x coordinate of the frame
4214 * @param {number} y - The y coordinate of the frame
4215 * @param {number} width - The width of the frame
4216 * @param {number} height - The height of the frame
4217 */
4218 _setNewFrame(x, y, width, height) {
4219 const frame = this._frame;
4220 [frame.x, frame.y, frame.width, frame.height] = [x, y, width, height];
4221 this._refresh();
4222 } // _setNewFrame
4223
4224 /**
4225 * This method shouldn't be called without an existing bitmap
4226 * Idempotent
4227 * @author DoubleX @since 0.9.5 @version 0.9.5
4228 */
4229 _onExistingBitmapChange() {
4230 this._refreshFrame = true;
4231 this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
4232 } // _onExistingBitmapChange
4233
4234 /**
4235 * Triggers the events upon removing this sprite bitmap
4236 * Idempotent
4237 * @author DoubleX @since 0.9.5 @version 0.9.5
4238 */
4239 _onNullBitmapChange() {
4240 this._refreshFrame = false;
4241 this.texture.frame = new Rectangle();
4242 } // _onNullBitmapChange
4243
4244 /**
4245 * Triggers the events upon just finished loading this sprite bitmap
4246 * Idempotent
4247 * @author DoubleX @since 0.9.5 @version 0.9.5
4248 */
4249 _onThisBitmapLoaded() {
4250 /** @todo Checks if bitmapLoaded in _onBitmapLoad is indeed nullable */
4251 if (!this._refreshFrame || !this._bitmap) return;
4252 //
4253 this._refreshFrame = false;
4254 this._frame.width = this._bitmap.width;
4255 this._frame.height = this._bitmap.height;
4256 } // _onThisBitmapLoaded
4257
4258 /**
4259 * This method shouldn't be called without an existing texture
4260 * Idempotent
4261 * @author DoubleX @since 0.9.5 @version 0.9.5
4262 */
4263 _refreshWithTexture() {
4264 const frameX = Math.floor(this._frame.x);
4265 const frameY = Math.floor(this._frame.y);
4266 const frameW = Math.floor(this._frame.width);
4267 const frameH = Math.floor(this._frame.height);
4268 const baseTexture = this._bitmap ? this._bitmap.baseTexture : null;
4269 const baseTextureW = baseTexture ? baseTexture.width : 0;
4270 const baseTextureH = baseTexture ? baseTexture.height : 0;
4271 const realX = frameX.clamp(0, baseTextureW);
4272 const realY = frameY.clamp(0, baseTextureH);
4273 const realW = (frameW - realX + frameX).clamp(0, baseTextureW - realX);
4274 const realH = (frameH - realY + frameY).clamp(0, baseTextureH - realY);
4275 const frame = new Rectangle(realX, realY, realW, realH);
4276 [this.pivot.x, this.pivot.y] = [frameX - realX, frameY - realY];
4277 if (baseTexture) this._refreshWithBaseTexture(baseTexture, frame);
4278 this.texture._updateID++;
4279 } // _refreshWithTexture
4280
4281 /**
4282 * This method shouldn't be called without an existing texture
4283 * Idempotent
4284 * @author DoubleX @since 0.9.5 @version 0.9.5
4285 * @param {PIXI.BaseTexture} baseTexture - The sprite bitmap base texture
4286 * @param {Rectangle} frame - The rectangular frame with real dimensions
4287 */
4288 _refreshWithBaseTexture(baseTexture, frame) {
4289 const { texture } = this;
4290 texture.baseTexture = baseTexture;
4291 /** @todo Thinks of if at least logging the catch will be better */
4292 try {
4293 texture.frame = frame;
4294 } catch (e) { texture.frame = new Rectangle(); }
4295 //
4296 } // _refreshWithBaseTexture
4297
4298} // Sprite
4299// It's just to play safe in case of any plugin extending PIXI.Sprite in ES6 way
4300ES6ExtendedClassAlias.inherit(Sprite);
4301//
4302
4303/*----------------------------------------------------------------------------
4304 * # Rewritten class: Stage
4305 * - Rewrites it into the ES6 standard
4306 *----------------------------------------------------------------------------*/
4307
4308//-----------------------------------------------------------------------------
4309/**
4310 * The root object of the display tree.
4311 *
4312 * @class
4313 * @extends PIXI.Container
4314 */
4315class Stage extends PIXI.Container {
4316
4317 destroy() { super.destroy({ children: true, texture: true }); }
4318
4319} // Stage
4320// It's just to play safe in case of any plugin extending PIXI.Container in ES6
4321ES6ExtendedClassAlias.inherit(Stage);
4322//
4323
4324/*----------------------------------------------------------------------------
4325 * # Rewritten class: Tilemap
4326 * - Rewrites it into the ES6 standard
4327 *----------------------------------------------------------------------------*/
4328
4329//-----------------------------------------------------------------------------
4330/**
4331 * The tilemap which displays 2D tile-based game map.
4332 *
4333 * @class
4334 * @extends PIXI.Container
4335 */
4336class Tilemap extends PIXI.Container {
4337
4338 constructor() {
4339 super();
4340 // Edited to help plugins initialize variables in better ways
4341 this._initPrivateVars();
4342 this._initPublicVars();
4343 //
4344 this._createLayers();
4345 this.refresh();
4346 } // constructor
4347
4348 /**
4349 * The width of the tilemap.
4350 *
4351 * @type number
4352 * @name Tilemap#width
4353 */
4354 get width() { return this._width; }
4355 set width(value) { this._width = value; }
4356
4357 /**
4358 * The height of the tilemap.
4359 *
4360 * @type number
4361 * @name Tilemap#height
4362 */
4363 get height() { return this._height; }
4364 set height(value) { this._height = value; }
4365
4366 /**
4367 * Destroys the tilemap.
4368 */
4369 destroy() { super.destroy({ children: true, texture: true }); }
4370
4371 /**
4372 * Sets the tilemap data.
4373 *
4374 * @param {number} width - The width of the map in number of tiles.
4375 * @param {number} height - The height of the map in number of tiles.
4376 * @param {array} data - The one dimensional array for the map data.
4377 */
4378 setData(width, height, data) {
4379 [this._mapWidth, this._mapHeight] = [width, height];
4380 this._mapData = data;
4381 } // setData
4382
4383 /**
4384 * Checks whether the tileset is ready to render.
4385 *
4386 * @type boolean
4387 * @returns {boolean} True if the tilemap is ready.
4388 */
4389 isReady() {
4390 return this._bitmaps.every(bitmap => !bitmap || bitmap.isReady());
4391 } // isReady
4392
4393 /**
4394 * Updates the tilemap for each frame.
4395 */
4396 update() {
4397 this.animationCount++;
4398 /** @todo Figures out where does 30 come from */
4399 this.animationFrame = Math.floor(this.animationCount / 30);
4400 //
4401 this.children.forEach(child => { if (child.update) child.update(); });
4402 } // update
4403
4404 /**
4405 * Sets the bitmaps used as a tileset.
4406 *
4407 * @param {array} bitmaps - The array of the tileset bitmaps.
4408 */
4409 setBitmaps(bitmaps) {
4410 // [Note] We wait for the images to finish loading. Creating textures
4411 // from bitmaps that are not yet loaded here brings some maintenance
4412 // difficulties. e.g. PIXI overwrites img.onload internally.
4413 this._bitmaps = bitmaps;
4414 const listener = this._updateBitmaps.bind(this);
4415 this._bitmaps.forEach(bitmap => {
4416 if (!bitmap.isReady()) bitmap.addLoadListener(listener);
4417 });
4418 this._needsBitmapsUpdate = true;
4419 this._updateBitmaps();
4420 } // setBitmaps
4421
4422 /**
4423 * Forces to repaint the entire tilemap.
4424 */
4425 refresh() { this._needsRepaint = true; }
4426
4427 /**
4428 * Updates the transform on all children of this container for rendering.
4429 */
4430 updateTransform() {
4431 const [ox, oy] = [Math.ceil(this.origin.x), Math.ceil(this.origin.y)];
4432 const startX = Math.floor((ox - this._margin) / this._tileWidth);
4433 const startY = Math.floor((oy - this._margin) / this._tileHeight);
4434 this._upperLayer.x = this._lowerLayer.x = startX * this._tileWidth - ox;
4435 this._lowerLayer.y = startY * this._tileHeight - oy;
4436 this._upperLayer.y = this._lowerLayer.y;
4437 /** @todo Extracts these codes into well-named functions */
4438 if (this._needsRepaint ||
4439 this._lastAnimationFrame !== this.animationFrame ||
4440 this._lastStartX !== startX || this._lastStartY !== startY) {
4441 this._lastAnimationFrame = this.animationFrame;
4442 [this._lastStartX, this._lastStartY] = [startX, startY];
4443 this._addAllSpots(startX, startY);
4444 this._needsRepaint = false;
4445 }
4446 //
4447 this._sortChildren();
4448 super.updateTransform();
4449 } // updateTransform
4450
4451 _createLayers() {
4452 /*
4453 * [Z coordinate]
4454 * 0 : Lower tiles
4455 * 1 : Lower characters
4456 * 3 : Normal characters
4457 * 4 : Upper tiles
4458 * 5 : Upper characters
4459 * 6 : Airship shadow
4460 * 7 : Balloon
4461 * 8 : Animation
4462 * 9 : Destination
4463 */
4464 this._lowerLayer = new Tilemap.Layer();
4465 this._lowerLayer.z = 0;
4466 this._upperLayer = new Tilemap.Layer();
4467 this._upperLayer.z = 4;
4468 this.addChild(this._lowerLayer);
4469 this.addChild(this._upperLayer);
4470 this._needsRepaint = true;
4471 } // _createLayers
4472
4473 _updateBitmaps() {
4474 /** @todo Extracts these codes into well-named functions */
4475 if (!this._needsBitmapsUpdate || !this.isReady()) return;
4476 this._lowerLayer.setBitmaps(this._bitmaps);
4477 this._needsBitmapsUpdate = false;
4478 this._needsRepaint = true;
4479 //
4480 }
4481
4482 _addAllSpots(startX, startY) {
4483 this._lowerLayer.clear();
4484 this._upperLayer.clear();
4485 /** @todo Extracts these codes into a well-named function */
4486 const widthWithMatgin = this.width + this._margin * 2;
4487 const heightWithMatgin = this.height + this._margin * 2;
4488 const tileCols = Math.ceil(widthWithMatgin / this._tileWidth) + 1;
4489 const tileRows = Math.ceil(heightWithMatgin / this._tileHeight) + 1;
4490 for (let y = 0; y < tileRows; y++) {
4491 for (let x = 0; x < tileCols; x++) {
4492 this._addSpot(startX, startY, x, y);
4493 }
4494 }
4495 //
4496 } // _addAllSpots
4497
4498 _addSpot(startX, startY, x, y) {
4499 const [mx, my] = [startX + x, startY + y];
4500 const [dx, dy] = [x * this._tileWidth, y * this._tileHeight];
4501 const tileId0 = this._readMapData(mx, my, 0);
4502 const tileId1 = this._readMapData(mx, my, 1);
4503 const tileId2 = this._readMapData(mx, my, 2);
4504 const tileId3 = this._readMapData(mx, my, 3);
4505 const shadowBits = this._readMapData(mx, my, 4);
4506 const upperTileId1 = this._readMapData(mx, my - 1, 1);
4507 this._addSpotTile(tileId0, dx, dy);
4508 this._addSpotTile(tileId1, dx, dy);
4509 this._addShadow(this._lowerLayer, shadowBits, dx, dy);
4510 /** @todo Extracts these codes into well-named functions */
4511 if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
4512 if (!Tilemap.isShadowingTile(tileId0)) {
4513 this._addTableEdge(this._lowerLayer, upperTileId1, dx, dy);
4514 }
4515 }
4516 //
4517 /** @todo Extracts these codes into well-named functions */
4518 if (this._isOverpassPosition(mx, my)) {
4519 this._addTile(this._upperLayer, tileId2, dx, dy);
4520 this._addTile(this._upperLayer, tileId3, dx, dy);
4521 } else {
4522 this._addSpotTile(tileId2, dx, dy);
4523 this._addSpotTile(tileId3, dx, dy);
4524 }
4525 //
4526 } // _addSpot
4527
4528 _addSpotTile(tileId, dx, dy) {
4529 if (this._isHigherTile(tileId)) {
4530 this._addTile(this._upperLayer, tileId, dx, dy);
4531 } else this._addTile(this._lowerLayer, tileId, dx, dy);
4532 } // _addSpotTile
4533
4534 _addTile(layer, tileId, dx, dy) {
4535 // Edited to help plugins alter add tile behaviors in better ways
4536 if (!Tilemap.isVisibleTile(tileId)) return;
4537 this._addVisibleTile(layer, tileId, dx, dy);
4538 //
4539 } // _addTile
4540
4541 _addNormalTile(layer, tileId, dx, dy) {
4542 const setNumber = Tilemap.isTileA5(tileId) ? 4 : 5 + Math.floor(tileId / 256);
4543 const [w, h] = [this._tileWidth, this._tileHeight];
4544 const sx = ((Math.floor(tileId / 128) % 2) * 8 + (tileId % 8)) * w;
4545 const sy = (Math.floor((tileId % 256) / 8) % 16) * h;
4546 layer.addRect(setNumber, sx, sy, dx, dy, w, h);
4547 } // _addNormalTile
4548
4549 _addAutotile(layer, tileId, dx, dy) {
4550 /** @todo Breaks the codes into several smaller well-named functions */
4551 const kind = Tilemap.getAutotileKind(tileId);
4552 const shape = Tilemap.getAutotileShape(tileId);
4553 const [tx, ty] = [kind % 8, Math.floor(kind / 8)];
4554 let setNumber = bx = by = 0;
4555 let [autotileTable, isTable] = [Tilemap.FLOOR_AUTOTILE_TABLE, false];
4556 if (Tilemap.isTileA1(tileId)) {
4557 const waterSurfaceIndex = [0, 1, 2, 1][this.animationFrame % 4];
4558 setNumber = 0;
4559 if (kind === 0) {
4560 [bx, by] = [waterSurfaceIndex * 2, 0];
4561 } else if (kind === 1) {
4562 [bx, by] = [waterSurfaceIndex * 2, 3];
4563 } else if (kind === 2) {
4564 [bx, by] = [6, 0];
4565 } else if (kind === 3) {
4566 [bx, by] = [6, 3];
4567 } else {
4568 bx = Math.floor(tx / 4) * 8;
4569 by = ty * 6 + (Math.floor(tx / 2) % 2) * 3;
4570 if (kind % 2 === 0) {
4571 bx += waterSurfaceIndex * 2;
4572 } else {
4573 bx += 6;
4574 autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
4575 by += this.animationFrame % 3;
4576 }
4577 }
4578 } else if (Tilemap.isTileA2(tileId)) {
4579 [setNumber, bx, by] = [1, tx * 2, (ty - 2) * 3];
4580 isTable = this._isTableTile(tileId);
4581 } else if (Tilemap.isTileA3(tileId)) {
4582 [setNumber, bx, by] = [2, tx * 2, (ty - 6) * 2];
4583 bx = tx * 2;
4584 by = (ty - 6) * 2;
4585 autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
4586 } else if (Tilemap.isTileA4(tileId)) {
4587 [setNumber, bx] = [3, tx * 2];
4588 by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
4589 if (ty % 2 === 1) autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
4590 }
4591 const table = autotileTable[shape];
4592 const [w1, h1] = [this._tileWidth / 2, this._tileHeight / 2];
4593 for (let i = 0; i < 4; i++) {
4594 const [qsx, qsy] = table[i];
4595 const [sx1, sy1] = [(bx * 2 + qsx) * w1, (by * 2 + qsy) * h1];
4596 const [dx1, dy1] = [dx + (i % 2) * w1, dy + Math.floor(i / 2) * h1];
4597 if (isTable && (qsy === 1 || qsy === 5)) {
4598 const [qsx2, qsy2] = [qsy === 1 ? (4 - qsx) % 4 : qsx, 3];
4599 const [sx2, sy2] = [(bx * 2 + qsx2) * w1, (by * 2 + qsy2) * h1];
4600 layer.addRect(setNumber, sx2, sy2, dx1, dy1, w1, h1);
4601 layer.addRect(setNumber, sx1, sy1, dx1, dy1 + h1 / 2, w1, h1 / 2);
4602 } else layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1);
4603 }
4604 //
4605 } // _addAutotile
4606
4607 _addTableEdge(layer, tileId, dx, dy) {
4608 // Edited to help plugins alter add table edge behaviors in better ways
4609 if (!Tilemap.isTileA2(tileId)) return;
4610 this._addTileA2TableEdge(layer, tileId, dx, dy);
4611 //
4612 } // _addTableEdge
4613
4614 _addShadow(layer, shadowBits, dx, dy) {
4615 /** @todo Figures out where does 0x0f come from */
4616 if (!(shadowBits & 0x0f)) return;
4617 //
4618 const [w1, h1] = [this._tileWidth / 2, this._tileHeight / 2];
4619 for (let i = 0; i < 4; i++) {
4620 if (!(shadowBits & (1 << i))) continue;
4621 const [dx1, dy1] = [dx + (i % 2) * w1, dy + Math.floor(i / 2) * h1];
4622 layer.addRect(-1, 0, 0, dx1, dy1, w1, h1);
4623 }
4624 } // _addShadow
4625
4626 _readMapData(x, y, z) {
4627 if (!this._mapData) return 0;
4628 const [width, height] = [this._mapWidth, this._mapHeight];
4629 if (this.horizontalWrap) x = x.mod(width);
4630 if (this.verticalWrap) y = y.mod(height);
4631 if (x < 0 || x >= width || y < 0 || y >= height) return 0;
4632 return this._mapData[(z * height + y) * width + x] || 0;
4633 } // _readMapData
4634
4635 _isHigherTile(tileId) { return this.flags[tileId] & 0x10; }
4636
4637 _isTableTile(tileId) {
4638 /** @todo Figures out where does 0x80 come from */
4639 return this.isTileA2(tileId) && this.flags[tileId] & 0x80;
4640 //
4641 } // _isTableTile
4642
4643 _isOverpassPosition(/*mx, my*/) { return false; }
4644
4645 _sortChildren() { this.children.sort(this._compareChildOrder.bind(this)); }
4646
4647 _compareChildOrder(a, b) {
4648 if (a.z !== b.z) return a.z - b.z;
4649 if (a.y !== b.y) return a.y - b.y;
4650 return a.spriteId - b.spriteId;
4651 } // _compareChildOrder
4652
4653 //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
4654 // Tile type checkers
4655 static TILE_ID_B = 0;
4656 static TILE_ID_C = 256;
4657 static TILE_ID_D = 512;
4658 static TILE_ID_E = 768;
4659 static TILE_ID_A5 = 1536;
4660 static TILE_ID_A1 = 2048;
4661 static TILE_ID_A2 = 2816;
4662 static TILE_ID_A3 = 4352;
4663 static TILE_ID_A4 = 5888;
4664 static TILE_ID_MAX = 8192;
4665
4666 static isVisibleTile(tileId) {
4667 return tileId > 0 && tileId < this.TILE_ID_MAX;
4668 } // isVisibleTile
4669
4670 static isAutotile(tileId) { return tileId >= this.TILE_ID_A1; }
4671
4672 static getAutotileKind(tileId) {
4673 return Math.floor((tileId - this.TILE_ID_A1) / 48);
4674 } // getAutotileKind
4675
4676 static getAutotileShape(tileId) { return (tileId - this.TILE_ID_A1) % 48; }
4677
4678 static makeAutotileId(kind, shape) {
4679 return this.TILE_ID_A1 + kind * 48 + shape;
4680 } // makeAutotileId
4681
4682 static isSameKindTile(tileID1, tileID2) {
4683 if (this.isAutotile(tileID1) && this.isAutotile(tileID2)) {
4684 return this.getAutotileKind(tileID1) === this.getAutotileKind(tileID2);
4685 } else return tileID1 === tileID2;
4686 } // isSameKindTile
4687
4688 static isTileA1(tileId) {
4689 return tileId >= this.TILE_ID_A1 && tileId < this.TILE_ID_A2;
4690 } // isTileA1
4691
4692 static isTileA2(tileId) {
4693 return tileId >= this.TILE_ID_A2 && tileId < this.TILE_ID_A3;
4694 } // isTileA2
4695
4696 static isTileA3(tileId) {
4697 return tileId >= this.TILE_ID_A3 && tileId < this.TILE_ID_A4;
4698 } // isTileA3
4699
4700 static isTileA4(tileId) {
4701 return tileId >= this.TILE_ID_A4 && tileId < this.TILE_ID_MAX;
4702 } // isTileA4
4703
4704 static isTileA5(tileId) {
4705 return tileId >= this.TILE_ID_A5 && tileId < this.TILE_ID_A1;
4706 } // isTileA5
4707
4708 static isWaterTile(tileId) {
4709 if (!this.isTileA1(tileId)) return false;
4710 if (tileId < this.TILE_ID_A1 + 96) return false;
4711 return tileId < this.TILE_ID_A1 + 192;
4712 } // isWaterTile
4713
4714 static isWaterfallTile(tileId) {
4715 if (tileId >= this.TILE_ID_A1 + 192 && tileId < this.TILE_ID_A2) {
4716 return this.getAutotileKind(tileId) % 2 === 1;
4717 } else return false;
4718 } // isWaterfallTile
4719
4720 static isGroundTile(tileId) {
4721 if (this.isTileA1(tileId) || this.isTileA2(tileId)) return true;
4722 return this.isTileA5(tileId);
4723 } // isGroundTile
4724
4725 static isShadowingTile(tileId) {
4726 return this.isTileA3(tileId) || this.isTileA4(tileId);
4727 } // isShadowingTile
4728
4729 static isRoofTile(tileId) {
4730 return this.isTileA3(tileId) && this.getAutotileKind(tileId) % 16 < 8;
4731 } // isRoofTile
4732
4733 static isWallTopTile(tileId) {
4734 return this.isTileA4(tileId) && this.getAutotileKind(tileId) % 16 < 8;
4735 } // isWallTopTile
4736
4737 static isWallSideTile(tileId) {
4738 if (!this.isTileA3(tileId) && !this.isTileA4(tileId)) return false;
4739 return this.getAutotileKind(tileId) % 16 >= 8;
4740 } // isWallSideTile
4741
4742 static isWallTile(tileId) {
4743 return this.isWallTopTile(tileId) || this.isWallSideTile(tileId);
4744 } // isWallTile
4745
4746 static isFloorTypeAutotile(tileId) {
4747 if (this.isTileA1(tileId) && !this.isWaterfallTile(tileId)) return true;
4748 return this.isTileA2(tileId) || this.isWallTopTile(tileId);
4749 } // isFloorTypeAutotile
4750
4751 static isWallTypeAutotile(tileId) {
4752 return this.isRoofTile(tileId) || this.isWallSideTile(tileId);
4753 } // isWallTypeAutotile
4754
4755 static isWaterfallTypeAutotile(tileId) {
4756 return this.isWaterfallTile(tileId);
4757 } // isWaterfallTypeAutotile
4758
4759 //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
4760 // Autotile shape number to coordinates of tileset images
4761 // prettier-ignore
4762 static FLOOR_AUTOTILE_TABLE = [
4763 [[2, 4], [1, 4], [2, 3], [1, 3]],
4764 [[2, 0], [1, 4], [2, 3], [1, 3]],
4765 [[2, 4], [3, 0], [2, 3], [1, 3]],
4766 [[2, 0], [3, 0], [2, 3], [1, 3]],
4767 [[2, 4], [1, 4], [2, 3], [3, 1]],
4768 [[2, 0], [1, 4], [2, 3], [3, 1]],
4769 [[2, 4], [3, 0], [2, 3], [3, 1]],
4770 [[2, 0], [3, 0], [2, 3], [3, 1]],
4771 [[2, 4], [1, 4], [2, 1], [1, 3]],
4772 [[2, 0], [1, 4], [2, 1], [1, 3]],
4773 [[2, 4], [3, 0], [2, 1], [1, 3]],
4774 [[2, 0], [3, 0], [2, 1], [1, 3]],
4775 [[2, 4], [1, 4], [2, 1], [3, 1]],
4776 [[2, 0], [1, 4], [2, 1], [3, 1]],
4777 [[2, 4], [3, 0], [2, 1], [3, 1]],
4778 [[2, 0], [3, 0], [2, 1], [3, 1]],
4779 [[0, 4], [1, 4], [0, 3], [1, 3]],
4780 [[0, 4], [3, 0], [0, 3], [1, 3]],
4781 [[0, 4], [1, 4], [0, 3], [3, 1]],
4782 [[0, 4], [3, 0], [0, 3], [3, 1]],
4783 [[2, 2], [1, 2], [2, 3], [1, 3]],
4784 [[2, 2], [1, 2], [2, 3], [3, 1]],
4785 [[2, 2], [1, 2], [2, 1], [1, 3]],
4786 [[2, 2], [1, 2], [2, 1], [3, 1]],
4787 [[2, 4], [3, 4], [2, 3], [3, 3]],
4788 [[2, 4], [3, 4], [2, 1], [3, 3]],
4789 [[2, 0], [3, 4], [2, 3], [3, 3]],
4790 [[2, 0], [3, 4], [2, 1], [3, 3]],
4791 [[2, 4], [1, 4], [2, 5], [1, 5]],
4792 [[2, 0], [1, 4], [2, 5], [1, 5]],
4793 [[2, 4], [3, 0], [2, 5], [1, 5]],
4794 [[2, 0], [3, 0], [2, 5], [1, 5]],
4795 [[0, 4], [3, 4], [0, 3], [3, 3]],
4796 [[2, 2], [1, 2], [2, 5], [1, 5]],
4797 [[0, 2], [1, 2], [0, 3], [1, 3]],
4798 [[0, 2], [1, 2], [0, 3], [3, 1]],
4799 [[2, 2], [3, 2], [2, 3], [3, 3]],
4800 [[2, 2], [3, 2], [2, 1], [3, 3]],
4801 [[2, 4], [3, 4], [2, 5], [3, 5]],
4802 [[2, 0], [3, 4], [2, 5], [3, 5]],
4803 [[0, 4], [1, 4], [0, 5], [1, 5]],
4804 [[0, 4], [3, 0], [0, 5], [1, 5]],
4805 [[0, 2], [3, 2], [0, 3], [3, 3]],
4806 [[0, 2], [1, 2], [0, 5], [1, 5]],
4807 [[0, 4], [3, 4], [0, 5], [3, 5]],
4808 [[2, 2], [3, 2], [2, 5], [3, 5]],
4809 [[0, 2], [3, 2], [0, 5], [3, 5]],
4810 [[0, 0], [1, 0], [0, 1], [1, 1]]
4811 ]; // FLOOR_AUTOTILE_TABLE
4812 // prettier-ignore
4813 static WALL_AUTOTILE_TABLE = [
4814 [[2, 2], [1, 2], [2, 1], [1, 1]],
4815 [[0, 2], [1, 2], [0, 1], [1, 1]],
4816 [[2, 0], [1, 0], [2, 1], [1, 1]],
4817 [[0, 0], [1, 0], [0, 1], [1, 1]],
4818 [[2, 2], [3, 2], [2, 1], [3, 1]],
4819 [[0, 2], [3, 2], [0, 1], [3, 1]],
4820 [[2, 0], [3, 0], [2, 1], [3, 1]],
4821 [[0, 0], [3, 0], [0, 1], [3, 1]],
4822 [[2, 2], [1, 2], [2, 3], [1, 3]],
4823 [[0, 2], [1, 2], [0, 3], [1, 3]],
4824 [[2, 0], [1, 0], [2, 3], [1, 3]],
4825 [[0, 0], [1, 0], [0, 3], [1, 3]],
4826 [[2, 2], [3, 2], [2, 3], [3, 3]],
4827 [[0, 2], [3, 2], [0, 3], [3, 3]],
4828 [[2, 0], [3, 0], [2, 3], [3, 3]],
4829 [[0, 0], [3, 0], [0, 3], [3, 3]]
4830 ]; // WALL_AUTOTILE_TABLE
4831 // prettier-ignore
4832 static WATERFALL_AUTOTILE_TABLE = [
4833 [[2, 0], [1, 0], [2, 1], [1, 1]],
4834 [[0, 0], [1, 0], [0, 1], [1, 1]],
4835 [[2, 0], [3, 0], [2, 1], [3, 1]],
4836 [[0, 0], [3, 0], [0, 1], [3, 1]]
4837 ]; // WATERFALL_AUTOTILE_TABLE
4838
4839 /**
4840 * Initializes all private variables of this tilemap
4841 * Idempotent
4842 * @author DoubleX @since 0.9.5 @version 0.9.5
4843 */
4844 _initPrivateVars() {
4845 [this._width, this._height] = [Graphics.width, Graphics.height];
4846 [this._margin, this._tileWidth, this._tileHeight] = [20, 48, 48];
4847 this._mapWidth = this._mapHeight = 0;
4848 [this._mapData, this._bitmaps] = [null, []];
4849 } // _initPrivateVars
4850
4851 /**
4852 * Initializes all public variables of this tilemap
4853 * Idempotent
4854 * @author DoubleX @since 0.9.5 @version 0.9.5
4855 */
4856 _initPublicVars() {
4857 /**
4858 * The origin point of the tilemap for scrolling.
4859 *
4860 * @type Point
4861 */
4862 this.origin = new Point();
4863 /**
4864 * The tileset flags.
4865 *
4866 * @type array
4867 */
4868 this.flags = [];
4869 /**
4870 * The animation count for autotiles.
4871 *
4872 * @type number
4873 */
4874 this.animationCount = 0;
4875 /**
4876 * Whether the tilemap loops horizontal.
4877 *
4878 * @type boolean
4879 */
4880 this.horizontalWrap = false;
4881 /**
4882 * Whether the tilemap loops vertical.
4883 *
4884 * @type boolean
4885 */
4886 this.verticalWrap = false;
4887 } // _initPublicVars
4888
4889 /**
4890 * Adds the visible tile at the specified positions on the specified layer
4891 * Idempotent
4892 * @author DoubleX @since 0.9.5 @version 0.9.5
4893 * @param {Tilemap.Layer} later - The layer having this visible tile added
4894 * @param {id} tileId - The id of the visible tile to be added
4895 * @param {number} dx - The x position of the visible tile to be added
4896 * @param {number} dy - The y position of the visible tile to be added
4897 */
4898 _addVisibleTile(layer, tileId, dx, dy) {
4899 if (Tilemap.isAutotile(tileId)) {
4900 this._addAutotile(layer, tileId, dx, dy);
4901 } else this._addNormalTile(layer, tileId, dx, dy);
4902 } // _addVisibleTile
4903
4904 /**
4905 * Adds the A2 table edge tile at specified positions on the specified layer
4906 * Idempotent
4907 * @author DoubleX @since 0.9.5 @version 0.9.5
4908 * @param {Tilemap.Layer} later - The layer having this visible tile added
4909 * @param {id} tileId - The id of the visible tile to be added
4910 * @param {number} dx - The x position of the visible tile to be added
4911 * @param {number} dy - The y position of the visible tile to be added
4912 */
4913 _addTileA2TableEdge(layer, tileId, dx, dy) {
4914 const autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
4915 const kind = Tilemap.getAutotileKind(tileId);
4916 const shape = Tilemap.getAutotileShape(tileId);
4917 const [tx, ty, setNumber] = [kind % 8, Math.floor(kind / 8), 1];
4918 const [bx, by, table] = [tx * 2, (ty - 2) * 3, autotileTable[shape]];
4919 const [w1, h1] = [this._tileWidth / 2, this._tileHeight / 2];
4920 for (let i = 0; i < 2; i++) {
4921 const [qsx, qsy] = table[2 + i];
4922 const sx1 = (bx * 2 + qsx) * w1;
4923 const sy1 = (by * 2 + qsy) * h1 + h1 / 2;
4924 const [dx1, dy1] = [dx + (i % 2) * w1, dy + Math.floor(i / 2) * h1];
4925 layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1 / 2);
4926 }
4927 } // _addTileA2TableEdge
4928
4929} // Tilemap
4930// It's just to play safe in case of any plugin extending PIXI.Container in ES6
4931ES6ExtendedClassAlias.inherit(Tilemap);
4932//
4933
4934/*----------------------------------------------------------------------------
4935 * # Rewritten class: Tilemap.Layer
4936 * - Rewrites it into the ES6 standard
4937 *----------------------------------------------------------------------------*/
4938
4939//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
4940// Internal classes
4941Tilemap.Layer = class extends PIXI.Container {
4942
4943 constructor() {
4944 super();
4945 // Edited to help plugins initialize private variables in better ways
4946 this._initPrivateVars();
4947 //
4948 this._createVao();
4949 } // constructor
4950
4951 static MAX_GL_TEXTURES = 3;
4952 static VERTEX_STRIDE = 9 * 4;
4953
4954 destroy() {
4955 // Edited to help plugins alter existing vao destructions in better ways
4956 if (this._vao) this._destroyWithVao();
4957 //
4958 this._indexBuffer = this._vertexBuffer = this._vao = null;
4959 } // destroy
4960
4961 setBitmaps(bitmaps) {
4962 this._images = bitmaps.map(bitmap => bitmap.image || bitmap.canvas);
4963 this._needsTexturesUpdate = true;
4964 } // setBitmaps
4965
4966 clear() {
4967 this._elements.clear();
4968 this._needsVertexUpdate = true;
4969 } // clear
4970
4971 addRect(setNumber, sx, sy, dx, dy, w, h) {
4972 this._elements.push([setNumber, sx, sy, dx, dy, w, h]);
4973 } // addRect
4974
4975 render(renderer) {
4976 const gl = renderer.gl;
4977 const tilemapRenderer = renderer.plugins.rpgtilemap;
4978 const shader = tilemapRenderer.getShader();
4979 const matrix = shader.uniforms.uProjectionMatrix;
4980 renderer.batch.setObjectRenderer(tilemapRenderer);
4981 renderer.projection.projectionMatrix.copyTo(matrix);
4982 matrix.append(this.worldTransform);
4983 renderer.shader.bind(shader);
4984 // Edited to help plugins alter texture update behaviors in better ways
4985 this._checkTexturesUpdate(tilemapRenderer, renderer);
4986 //
4987 tilemapRenderer.bindTextures(renderer);
4988 renderer.geometry.bind(this._vao, shader);
4989 this._updateIndexBuffer();
4990 // Edited to help plugins alter vertex update behaviors in better ways
4991 this._checkVertexUpdate();
4992 //
4993 renderer.geometry.updateBuffers();
4994 const numElements = this._elements.length;
4995 // Edited to help plugins alter render element behaviors in better ways
4996 if (numElements > 0) this._renderWithElems(renderer, gl, numElements);
4997 //
4998 } // render
4999
5000 isReady() {
5001 if (this._images.isEmpty()) return false;
5002 return this._images.every(texture => texture && texture.valid);
5003 } // isReady
5004
5005 _createVao() {
5006 this._indexBuffer = new PIXI.Buffer(null, true, true);
5007 this._vertexBuffer = new PIXI.Buffer(null, true, false);
5008 // Edited to help plugins alter vao create behaviors in better ways
5009 this._vao = this._newVao(this._indexBuffer, this._vertexBuffer);
5010 //
5011 } // _createVao
5012
5013 _updateIndexBuffer() {
5014 const numElements = this._elements.length;
5015 /** @todo Extracts these codes into well-named functions */
5016 if (this._indexArray.length >= numElements * 6 * 2) return;
5017 this._indexArray = PIXI.utils.createIndicesForQuads(numElements * 2);
5018 this._indexBuffer.update(this._indexArray);
5019 //
5020 } // _updateIndexBuffer
5021
5022 _updateVertexBuffer() {
5023 const numElements = this._elements.length;
5024 const required = numElements * Tilemap.Layer.VERTEX_STRIDE;
5025 if (this._vertexArray.length < required) {
5026 this._vertexArray = new Float32Array(required * 2);
5027 }
5028 const [vertexArray, repeatIs] = [this._vertexArray, [0, 9, 18, 27]];
5029 this._elements.forEach((item, i) => {
5030 const setNumber = item[0], tid = setNumber >> 2;
5031 const sxOffset = 1024 * (setNumber & 1);
5032 const syOffset = 1024 * ((setNumber >> 1) & 1);
5033 const [sx, sy] = [item[1] + sxOffset, item[2] + syOffset];
5034 const [dx, dy, w, h] = [item[3], item[4], item[5], item[6]];
5035 const [frameLeft, frameTop] = [sx + 0.5, sy + 0.5];
5036 const [frameRight, frameBottom] = [sx + w - 0.5, sy + h - 0.5];
5037 const vertexArrIStart = 36 * i;
5038 const repeats = [tid, frameLeft, frameTop, frameRight, frameBottom];
5039 repeatIs.forEach(i => {
5040 repeats.forEach((val, j) => {
5041 vertexArray[vertexArrIStart + i + j] = val;
5042 });
5043 });
5044 vertexArray[vertexArrIStart + 5] = sx;
5045 vertexArray[vertexArrIStart + 6] = sy;
5046 vertexArray[vertexArrIStart + 7] = dx;
5047 vertexArray[vertexArrIStart + 8] = dy;
5048 vertexArray[vertexArrIStart + 14] = sx + w;
5049 vertexArray[vertexArrIStart + 15] = sy;
5050 vertexArray[vertexArrIStart + 16] = dx + w;
5051 vertexArray[vertexArrIStart + 17] = dy;
5052 vertexArray[vertexArrIStart + 23] = sx + w;
5053 vertexArray[vertexArrIStart + 24] = sy + h;
5054 vertexArray[vertexArrIStart + 25] = dx + w;
5055 vertexArray[vertexArrIStart + 26] = dy + h;
5056 vertexArray[vertexArrIStart + 32] = sx;
5057 vertexArray[vertexArrIStart + 33] = sy + h;
5058 vertexArray[vertexArrIStart + 34] = dx;
5059 vertexArray[vertexArrIStart + 35] = dy + h;
5060 });
5061 this._vertexBuffer.update(vertexArray);
5062 } // _updateVertexBuffer
5063
5064 /**
5065 * Initializes all private variables of this tilemap layer
5066 * Idempotent
5067 * @author DoubleX @since 0.9.5 @version 0.9.5
5068 */
5069 _initPrivateVars() {
5070 this._elements = [];
5071 [this._indexBuffer, this._indexArray] = [null, new Float32Array(0)];
5072 [this._vertexBuffer, this._vertexArray] = [null, new Float32Array(0)];
5073 this._vao = null;
5074 this._needsTexturesUpdate = this._needsVertexUpdate = false;
5075 [this._images, this._state] = [[], PIXI.State.for2d()];
5076 } // _initPrivateVars
5077
5078 /**
5079 * This method shouldn't be called without an existing vao
5080 * Idempotent
5081 * @author DoubleX @since 0.9.5 @version 0.9.5
5082 */
5083 _destroyWithVao() {
5084 this._vao.destroy();
5085 this._indexBuffer.destroy();
5086 this._vertexBuffer.destroy();
5087 } // _destroyWithVao
5088
5089 /**
5090 * Checks if the textures need to be updated and do so if they're needed
5091 * Hotspot/Idempotent
5092 * @author DoubleX @since 0.9.5 @version 0.9.5
5093 * @param {Tilemap.Renderer} tilemapRenderer - The RMMZ tilemap renderer
5094 * @param {PIXI.Renderer} renderer - The pixi renderer
5095 */
5096 _checkTexturesUpdate(tilemapRenderer, renderer) {
5097 if (!this._needsTexturesUpdate) return;
5098 tilemapRenderer.updateTextures(renderer, this._images);
5099 this._needsTexturesUpdate = false;
5100 } // _checkTexturesUpdate
5101
5102 /**
5103 * Checks if the vertex needs to be updated and do so if it's needed
5104 * Hotspot/Idempotent
5105 * @author DoubleX @since 0.9.5 @version 0.9.5
5106 */
5107 _checkVertexUpdate() {
5108 if (!this._needsVertexUpdate) return;
5109 this._updateVertexBuffer();
5110 this._needsVertexUpdate = false;
5111 } // _checkVertexUpdate
5112
5113 /**
5114 * Checks if the textures need to be updated and do so if they're needed
5115 * Hotspot/Idempotent
5116 * @author DoubleX @since 0.9.5 @version 0.9.5
5117 * @param {PIXI.Renderer} renderer - The pixi renderer
5118 * @param {WebGLRenderingContext} gl - The pixi WebGL rendering context
5119 * @param {number} numElements - The number of rectangle elements
5120 */
5121 _renderWithElems(renderer, gl, numElements) {
5122 renderer.state.set(this._state);
5123 renderer.geometry.draw(gl.TRIANGLES, numElements * 6, 0);
5124 } // _renderWithElems
5125
5126 /**
5127 * Nullipotent
5128 * @author DoubleX @since 0.9.5 @version 0.9.5
5129 * @param {PIXI.Buffer} ib - The index buffer of this tilemap
5130 * @param {PIXI.Buffer} vb - The vertex buffer of this tilemap
5131 */
5132 _newVao(ib, vb) {
5133 const [stride, type] = [Tilemap.Layer.VERTEX_STRIDE, PIXI.TYPES.FLOAT];
5134 return new PIXI.Geometry()
5135 .addIndex(ib)
5136 .addAttribute("aTextureId", vb, 1, false, type, stride, 0)
5137 .addAttribute("aFrame", vb, 4, false, type, stride, 1 * 4)
5138 .addAttribute("aSource", vb, 2, false, type, stride, 5 * 4)
5139 .addAttribute("aDest", vb, 2, false, type, stride, 7 * 4);
5140 } // _newVao
5141
5142}; // Tilemap.Layer
5143// It's just to play safe in case of any plugin extending PIXI.Container in ES6
5144ES6ExtendedClassAlias.inherit(Tilemap.Layer);
5145//
5146
5147/*----------------------------------------------------------------------------
5148 * # Rewritten class: Tilemap.Renderer
5149 * - Rewrites it into the ES6 standard
5150 *----------------------------------------------------------------------------*/
5151
5152//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
5153// Internal classes
5154Tilemap.Renderer = class extends PIXI.ObjectRenderer {
5155
5156 constructor(renderer) {
5157 super(renderer);
5158 // Edited to help plugins initialize private variables in better ways
5159 this._initPrivateVars();
5160 //
5161 this.contextChange();
5162 } // constructor
5163
5164 destroy() {
5165 super.destroy();
5166 this._destroyInternalTextures();
5167 this._shader.destroy();
5168 this._shader = null;
5169 } // destroy
5170
5171 getShader() { return this._shader; }
5172
5173 contextChange() {
5174 [this._shader, this._images] = [this._createShader(), []];
5175 this._createInternalTextures();
5176 } // contextChange
5177
5178 _createShader() {
5179 // Edited to help plugins alter the create shader glsl in better ways
5180 const vertexSrc = this._shaderVertexSrc();
5181 const fragmentSrc = this._shaderFragmentSrc();
5182 //
5183 return new PIXI.Shader(PIXI.Program.from(vertexSrc, fragmentSrc), {
5184 uSampler0: 0,
5185 uSampler1: 1,
5186 uSampler2: 2,
5187 uProjectionMatrix: new PIXI.Matrix()
5188 });
5189 } // _createShader
5190
5191 _createInternalTextures() {
5192 this._destroyInternalTextures();
5193 const maxGLTextures = Tilemap.Layer.MAX_GL_TEXTURES;
5194 for (let i = 0; i < maxGLTextures; i++) {
5195 // Edited to help plugins alter internal base textures in better way
5196 this._internalTextures.push(this._internalBaseTexture());
5197 //
5198 }
5199 } // _createInternalTextures
5200
5201 _destroyInternalTextures() {
5202 this._internalTextures.forEach(internalTexture => {
5203 internalTexture.destroy();
5204 });
5205 this._internalTextures = [];
5206 } // _destroyInternalTextures
5207
5208 updateTextures(renderer, images) {
5209 images.forEach((image, i) => {
5210 // Edited to help plugins alter texture updates in better ways
5211 this._updateTextureImage(renderer, image, i);
5212 //
5213 });
5214 } // updateTextures
5215
5216 bindTextures(renderer) {
5217 const maxGLTextures = Tilemap.Layer.MAX_GL_TEXTURES;
5218 for (let ti = 0; ti < maxGLTextures; ti++) {
5219 renderer.texture.bind(this._internalTextures[ti], ti);
5220 }
5221 } // bindTextures
5222
5223 /**
5224 * Initializes all private variables of this tilemap renderer
5225 * Idempotent
5226 * @author DoubleX @since 0.9.5 @version 0.9.5
5227 */
5228 _initPrivateVars() {
5229 [this._shader, this._images, this._internalTextures] = [null, [], []];
5230 /** @todo Figures out where does 4 come from */
5231 this._clearBuffer = new Uint8Array(1024 * 1024 * 4);
5232 //
5233 } // _initPrivateVars
5234
5235 /**
5236 * Nullipotent
5237 * @author DoubleX @since 0.9.5 @version 0.9.5
5238 * @returns {string} The shader vertex glsl source codes
5239 */
5240 _shaderVertexSrc() {
5241 return `attribute float aTextureId;
5242 attribute vec4 aFrame;
5243 attribute vec2 aSource;
5244 attribute vec2 aDest;
5245 uniform mat3 uProjectionMatrix;
5246 varying vec4 vFrame;
5247 varying vec2 vTextureCoord;
5248 varying float vTextureId;
5249 void main(void) {
5250 vec3 position = uProjectionMatrix * vec3(aDest, 1.0);
5251 gl_Position = vec4(position, 1.0);
5252 vFrame = aFrame;
5253 vTextureCoord = aSource;
5254 vTextureId = aTextureId;
5255 }`;
5256 } // _shaderVertexSrc
5257
5258 /**
5259 * Nullipotent
5260 * @author DoubleX @since 0.9.5 @version 0.9.5
5261 * @returns {string} The shader fragment glsl source codes
5262 */
5263 _shaderFragmentSrc() {
5264 return `varying vec4 vFrame;
5265 varying vec2 vTextureCoord;
5266 varying float vTextureId;
5267 uniform sampler2D uSampler0;
5268 uniform sampler2D uSampler1;
5269 uniform sampler2D uSampler2;
5270 void main(void) {
5271 vec2 textureCoord = clamp(vTextureCoord, vFrame.xy, vFrame.zw);
5272 int textureId = int(vTextureId);
5273 vec4 color;
5274 if (textureId < 0) {
5275 color = vec4(0.0, 0.0, 0.0, 0.5);
5276 } else if (textureId == 0) {
5277 color = texture2D(uSampler0, textureCoord / 2048.0);
5278 } else if (textureId == 1) {
5279 color = texture2D(uSampler1, textureCoord / 2048.0);
5280 } else if (textureId == 2) {
5281 color = texture2D(uSampler2, textureCoord / 2048.0);
5282 }
5283 gl_FragColor = color;
5284 }`;
5285 } // _shaderFragmentSrc
5286
5287 /**
5288 * Nullipotent
5289 * @author DoubleX @since 0.9.5 @version 0.9.5
5290 * @returns {PIXI.BaseRenderTexture} The internal base texture in the list
5291 */
5292 _internalBaseTexture() {
5293 const baseTexture = new PIXI.BaseRenderTexture();
5294 /** @todo Figures out where does 2048 come from */
5295 baseTexture.resize(2048, 2048);
5296 //
5297 baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
5298 return baseTexture;
5299 } // _internalBaseTexture
5300
5301 /**
5302 * Updates the specified texture image with the specified renderer
5303 * Hotspot/Idempotent
5304 * @author DoubleX @since 0.9.5 @version 0.9.5
5305 * @param {PIXI.Renderer} renderer - The pixi renderer
5306 * @param {HTMLImageElement|HTMLCanvasElement} image - The texture image
5307 * @param {index} i - The index of the texture image in the image list
5308 */
5309 _updateTextureImage(renderer, image, i) {
5310 renderer.texture.bind(this._internalTextures[i >> 2], 0);
5311 const [gl, x, y] = [renderer.gl, 1024 * (i % 2), 1024 * ((i >> 1) % 2)];
5312 const [format, type] = [gl.RGBA, gl.UNSIGNED_BYTE];
5313 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
5314 // prettier-ignore
5315 gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1024, 1024, format, type,
5316 this._clearBuffer);
5317 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
5318 gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, format, type, image);
5319 } // _updateTextureImage
5320
5321}; // Tilemap.Renderer
5322PIXI.Renderer.registerPlugin("rpgtilemap", Tilemap.Renderer);
5323// It's just to play safe in case of any plugin extending PIXI.ObjectRenderer
5324ES6ExtendedClassAlias.inherit(Tilemap.Renderer);
5325//
5326
5327/*----------------------------------------------------------------------------
5328 * # Rewritten class: TilingSprite
5329 * - Rewrites it into the ES6 standard
5330 *----------------------------------------------------------------------------*/
5331
5332//-----------------------------------------------------------------------------
5333/**
5334 * The sprite object for a tiling image.
5335 *
5336 * @class
5337 * @extends PIXI.TilingSprite
5338 * @param {Bitmap} bitmap - The image for the tiling sprite.
5339 */
5340class TilingSprite extends PIXI.TilingSprite {
5341
5342 constructor(bitmap) {
5343 TilingSprite._initEmptyBaseTexture();
5344 const frame = new Rectangle();
5345 const texture = new PIXI.Texture(TilingSprite._emptyBaseTexture, frame);
5346 PIXI.TilingSprite.call(this, texture);
5347 this._bitmap = bitmap;
5348 this._width = this._height = 0;
5349 this._frame = frame;
5350 this._initPublicVars();
5351 this._onBitmapChange();
5352 } // constructor
5353
5354 static _emptyBaseTexture = null;
5355
5356 /**
5357 * The image for the tiling sprite.
5358 *
5359 * @type Bitmap
5360 * @name TilingSprite#bitmap
5361 */
5362 get bitmap() { return this._bitmap; }
5363 set bitmap(value) {
5364 if (this._bitmap === value) return;
5365 this._bitmap = value;
5366 this._onBitmapChange();
5367 } // bitmap
5368
5369 /**
5370 * The opacity of the tiling sprite (0 to 255).
5371 *
5372 * @type number
5373 * @name TilingSprite#opacity
5374 */
5375 get opacity() { return this.alpha * 255; }
5376 set opacity(value) { this.alpha = value.clamp(0, 255) / 255; }
5377
5378 /**
5379 * Destroys the tiling sprite.
5380 */
5381 destroy() { super.destroy({ children: true, texture: true }); }
5382
5383 /**
5384 * Updates the tiling sprite for each frame.
5385 */
5386 update() {
5387 /** @todo Checks if an invisible sprite needs to be updated */
5388 this.children.forEach(child => { if (child.update) child.update(); });
5389 //
5390 } // update
5391
5392 /**
5393 * Sets the x, y, width, and height all at once.
5394 *
5395 * @param {number} x - The x coordinate of the tiling sprite.
5396 * @param {number} y - The y coordinate of the tiling sprite.
5397 * @param {number} width - The width of the tiling sprite.
5398 * @param {number} height - The height of the tiling sprite.
5399 */
5400 move(x = 0, y = 0, width = 0, height = 0) {
5401 // They shouldn't be "", false, null, NaN or other defined falsy values
5402 [this.x, this.y, this._width, this._height] = [x, y, width, height];
5403 //
5404 } // move
5405
5406 /**
5407 * Specifies the region of the image that the tiling sprite will use.
5408 *
5409 * @param {number} x - The x coordinate of the frame.
5410 * @param {number} y - The y coordinate of the frame.
5411 * @param {number} width - The width of the frame.
5412 * @param {number} height - The height of the frame.
5413 */
5414 setFrame(x, y, width, height) {
5415 [this._frame.x, this._frame.y] = [x, y];
5416 [this._frame.width, this._frame.height] = [width, height];
5417 this._refresh();
5418 } // setFrame
5419
5420 /**
5421 * Updates the transform on all children of this container for rendering.
5422 */
5423 updateTransform() {
5424 this.tilePosition.x = Math.round(-this.origin.x);
5425 this.tilePosition.y = Math.round(-this.origin.y);
5426 super.updateTransform();
5427 } // updateTransform
5428
5429 _onBitmapChange() {
5430 if (!this._bitmap) return this.texture.frame = new Rectangle();
5431 this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
5432 } // _onBitmapChange
5433
5434 _onBitmapLoad() {
5435 this.texture.baseTexture = this._bitmap.baseTexture;
5436 this._refresh();
5437 } // _onBitmapLoad
5438
5439 _refresh() {
5440 // Edited to help plugins alter texture refresh behaviors in better ways
5441 if (this.texture) this._refreshWithTexture();
5442 //
5443 } // _refresh
5444
5445 /**
5446 * Initializes the new static empty base texture of the Sprite class
5447 * Idempotent
5448 * @author DoubleX @since 0.9.5 @version 0.9.5
5449 */
5450 static _initEmptyBaseTexture() {
5451 if (this._emptyBaseTexture) return;
5452 this._emptyBaseTexture = this._newEmptyBaseTexture();
5453 } // _initEmptyBaseTexture
5454
5455 /**
5456 * Nullipotent
5457 * @author DoubleX @since 0.9.5 @version 0.9.5
5458 * @returns {PIXI.BaseTexture} The new static empty base texture of Sprite
5459 */
5460 static _newEmptyBaseTexture() {
5461 const emptyBaseTexture = new PIXI.BaseTexture();
5462 emptyBaseTexture.setSize(1, 1);
5463 return emptyBaseTexture;
5464 } // _newEmptyBaseTexture
5465
5466 /**
5467 * Initializes all public variables of this tiling sprite
5468 * Idempotent
5469 * @author DoubleX @since 0.9.5 @version 0.9.5
5470 */
5471 _initPublicVars() {
5472 /**
5473 * The origin point of the tiling sprite for scrolling.
5474 *
5475 * @type Point
5476 */
5477 this.origin = new Point();
5478 } // _initPublicVars
5479
5480 /**
5481 * This method shouldn't be called without an existing texture
5482 * Idempotent
5483 * @author DoubleX @since 0.9.5 @version 0.9.5
5484 */
5485 _refreshWithTexture() {
5486 if (this.texture.baseTexture) this._refreshWithBaseTexture();
5487 this.texture._updateID++;
5488 } // _refreshWithTexture
5489
5490 /**
5491 * This method shouldn't be called without an existing base texture
5492 * Idempotent
5493 * @author DoubleX @since 0.9.5 @version 0.9.5
5494 */
5495 _refreshWithBaseTexture() {
5496 /** @todo Thinks of if at least logging the catch will be better */
5497 try {
5498 this.texture.frame = this._refreshedTextureFrame();
5499 } catch (e) { this.texture.frame = new Rectangle(); }
5500 //
5501 } // _refreshWithBaseTexture
5502
5503 /**
5504 * Nullipotent
5505 * @author DoubleX @since 0.9.5 @version 0.9.5
5506 * @returns {Rectangle} The new frame for the refreshed texture
5507 */
5508 _refreshedTextureFrame() {
5509 const frame = this._frame.clone();
5510 if (frame.width === 0 && frame.height === 0 && this._bitmap) {
5511 frame.width = this._bitmap.width;
5512 frame.height = this._bitmap.height;
5513 }
5514 return frame;
5515 } // _refreshedTextureFrame
5516
5517} // TilingSprite
5518// It's just to play safe in case of any plugin extending PIXI.TilingSprite
5519ES6ExtendedClassAlias.inherit(TilingSprite);
5520//
5521
5522/*----------------------------------------------------------------------------
5523 * # Rewritten class: Weather
5524 * - Rewrites it into the ES6 standard
5525 *----------------------------------------------------------------------------*/
5526
5527//-----------------------------------------------------------------------------
5528/**
5529 * The weather effect which displays rain, storm, or snow.
5530 *
5531 * @class
5532 * @extends PIXI.Container
5533 */
5534class Weather extends PIXI.Container {
5535
5536 constructor() {
5537 super();
5538 // Edited to help plugins initialize private variables in better ways
5539 this._initPrivateVars();
5540 //
5541 this._createBitmaps();
5542 this._createDimmer();
5543 // Edited to help plugins initialize public variables in better ways
5544 this._initPublicVars();
5545 //
5546 } // constructor
5547
5548 /**
5549 * Destroys the weather.
5550 */
5551 destroy() {
5552 super.destroy({ children: true, texture: true });
5553 this._rainBitmap.destroy();
5554 this._stormBitmap.destroy();
5555 this._snowBitmap.destroy();
5556 } // destroy
5557
5558 /**
5559 * Updates the weather for each frame.
5560 */
5561 update() {
5562 this._updateDimmer();
5563 this._updateAllSprites();
5564 } // update
5565
5566 _createBitmaps() {
5567 this._rainBitmap = new Bitmap(1, 60);
5568 this._rainBitmap.fillAll("white");
5569 this._stormBitmap = new Bitmap(2, 100);
5570 this._stormBitmap.fillAll("white");
5571 this._snowBitmap = new Bitmap(9, 9);
5572 this._snowBitmap.drawCircle(4, 4, 4, "white");
5573 } // _createBitmaps
5574
5575 _createDimmer() {
5576 this._dimmerSprite = new ScreenSprite();
5577 this._dimmerSprite.setColor(80, 80, 80);
5578 this.addChild(this._dimmerSprite);
5579 } // _createDimmer
5580
5581 _updateDimmer() {
5582 this._dimmerSprite.opacity = Math.floor(this.power * 6);
5583 } // _updateDimmer
5584
5585 _updateAllSprites() {
5586 const maxSprites = Math.floor(this.power * 10);
5587 while (this._sprites.length < maxSprites) this._addSprite();
5588 while (this._sprites.length > maxSprites) this._removeSprite();
5589 this._sprites.forEach(sprite => {
5590 this._updateSprite(sprite);
5591 sprite.x = sprite.ax - this.origin.x;
5592 sprite.y = sprite.ay - this.origin.y;
5593 });
5594 } // _updateAllSprites
5595
5596 _addSprite() {
5597 // Edited to help plugins alter sprite add behaviors in better ways
5598 const sprite = this._newSprite();
5599 //
5600 this._sprites.push(sprite);
5601 this.addChild(sprite);
5602 } // _addSprite
5603
5604 _removeSprite() { this.removeChild(this._sprites.pop()); }
5605
5606 _updateSprite(sprite) {
5607 switch (this.type) {
5608 case "rain":
5609 this._updateRainSprite(sprite);
5610 break;
5611 case "storm":
5612 this._updateStormSprite(sprite);
5613 break;
5614 case "snow":
5615 this._updateSnowSprite(sprite);
5616 break;
5617 }
5618 /** @todo Figures out where does 40 come from */
5619 if (sprite.opacity < 40) this._rebornSprite(sprite);
5620 //
5621 } // _updateSprite
5622
5623 _updateRainSprite(sprite) {
5624 // Edited to dry up codes essentially being the identical knowledge
5625 this._updateWeatherSprite(sprite, this._rainBitmap, Math.PI / 16, 6);
5626 //
5627 } // _updateRainSprite
5628
5629 _updateStormSprite(sprite) {
5630 // Edited to dry up codes essentially being the identical knowledge
5631 this._updateWeatherSprite(sprite, this._stormBitmap, Math.PI / 8, 8);
5632 //
5633 } // _updateStormSprite
5634
5635 _updateSnowSprite(sprite) {
5636 // Edited to dry up codes essentially being the identical knowledge
5637 this._updateWeatherSprite(sprite, this._snowBitmap, Math.PI / 16, 3);
5638 //
5639 } // _updateSnowSprite
5640
5641 /**
5642 * Initializes all private variables of this weather
5643 * Idempotent
5644 * @author DoubleX @since 0.9.5 @version 0.9.5
5645 */
5646 _initPrivateVars() {
5647 [this._width, this._height] = [Graphics.width, Graphics.height];
5648 this._sprites = [];
5649 } // _initPrivateVars
5650
5651 /**
5652 * Initializes all public variables of this weather
5653 * Idempotent
5654 * @author DoubleX @since 0.9.5 @version 0.9.5
5655 */
5656 _initPublicVars() {
5657 /**
5658 * The type of the weather in ["none", "rain", "storm", "snow"].
5659 *
5660 * @type string
5661 */
5662 this.type = "none";
5663 /**
5664 * The power of the weather in the range (0, 9).
5665 *
5666 * @type number
5667 */
5668 this.power = 0;
5669 /**
5670 * The origin point of the tiling sprite for scrolling.
5671 *
5672 * @type Point
5673 */
5674 this.origin = new Point();
5675 } // _initPublicVars
5676
5677 /**
5678 * Nullipotent
5679 * @author DoubleX @since 0.9.5 @version 0.9.5
5680 * @returns {Sprite} The new weather sprite to be added in the list
5681 */
5682 _newSprite() {
5683 const sprite = new Sprite(this.viewport);
5684 sprite.opacity = 0;
5685 return sprite;
5686 } // _newSprite
5687
5688 /**
5689 * Updates the sprite with the specified weather, radian and opacity change
5690 * Idempotent
5691 * @author DoubleX @since 0.9.5 @version 0.9.5
5692 * @param {Sprite} sprite - The weather sprite to be updated
5693 * @param {Bitmap} bitmap - The new bitmap of the weather sprite
5694 * @param {number} radian - The new rotation of the weather sprite
5695 * @param {number} opacityDecrement - The weather sprite opacity decrement
5696 */
5697 _updateWeatherSprite(sprite, bitmap, radian, opacityDecrement) {
5698 [sprite.bitmap, sprite.rotation] = [bitmap, radian];
5699 sprite.ax -= opacityDecrement * Math.sin(sprite.rotation);
5700 sprite.ay += opacityDecrement * Math.cos(sprite.rotation);
5701 sprite.opacity -= opacityDecrement;
5702 } // _updateWeatherSprite
5703
5704} // Weather
5705// It's just to play safe in case of any plugin extending PIXI.Container in ES6
5706ES6ExtendedClassAlias.inherit(Weather);
5707//
5708
5709/*----------------------------------------------------------------------------
5710 * # Rewritten class: WebAudio
5711 * - Rewrites it into the ES6 standard
5712 *----------------------------------------------------------------------------*/
5713
5714//-----------------------------------------------------------------------------
5715/**
5716 * The audio object of Web Audio API.
5717 *
5718 * @class
5719 * @param {string} url - The url of the audio file.
5720 */
5721class WebAudio {
5722
5723 constructor(url) {
5724 this.clear();
5725 this._url = url;
5726 this._startLoading();
5727 } // constructor
5728
5729 /**
5730 * Initializes the audio system.
5731 *
5732 * @returns {boolean} True if the audio system is available.
5733 */
5734 static initialize() {
5735 this._context = this._masterGainNode = null;
5736 this._masterVolume = 1;
5737 this._createContext();
5738 this._createMasterGainNode();
5739 this._setupEventHandlers();
5740 return !!this._context;
5741 } // initialize
5742
5743 /**
5744 * Sets the master volume for all audio.
5745 *
5746 * @param {number} value - The master volume (0 to 1).
5747 */
5748 static setMasterVolume(value) {
5749 this._masterVolume = value;
5750 this._resetVolume();
5751 } // setMasterVolume
5752
5753 static _createContext() {
5754 try {
5755 const AudioContext = window.AudioContext || window.webkitAudioContext;
5756 this._context = new AudioContext();
5757 } catch (e) { this._context = null; }
5758 } // _createContext
5759
5760 static _currentTime() {
5761 return this._context ? this._context.currentTime : 0;
5762 } // _currentTime
5763
5764 static _createMasterGainNode() {
5765 // Edited to help plugins create master gain node in better ways
5766 if (this._context) this._createMasterGainNodeWithContext();
5767 //
5768 } // _createMasterGainNode
5769
5770 static _setupEventHandlers() {
5771 const onUserGesture = this._onUserGesture.bind(this);
5772 const onVisibilityChange = this._onVisibilityChange.bind(this);
5773 document.addEventListener("keydown", onUserGesture);
5774 document.addEventListener("mousedown", onUserGesture);
5775 document.addEventListener("touchend", onUserGesture);
5776 document.addEventListener("visibilitychange", onVisibilityChange);
5777 } // _setupEventHandlers
5778
5779 static _onUserGesture() {
5780 const context = this._context;
5781 if (context && context.state === "suspended") context.resume();
5782 } // _onUserGesture
5783
5784 static _onVisibilityChange() {
5785 if (document.visibilityState === "hidden") return this._onHide();
5786 this._onShow();
5787 } // _onVisibilityChange
5788
5789 static _onHide() {
5790 if (this._shouldMuteOnHide()) this._fadeOut(1);
5791 } // _onHide
5792
5793 static _onShow() {
5794 if (this._shouldMuteOnHide()) this._fadeIn(1);
5795 } // _onShow
5796
5797 static _shouldMuteOnHide() {
5798 return Utils.isMobileDevice() && !window.navigator.standalone;
5799 } // _shouldMuteOnHide
5800
5801 static _resetVolume() {
5802 // Edited to help plugins reset volume with gain node in better ways
5803 if (this._masterGainNode) this._resetVolWithMasterGainNode();
5804 //
5805 } // _resetColume
5806
5807 static _fadeIn(duration) {
5808 // Edited to help plugins fade in with master gain node in better ways
5809 if (this._masterGainNode) this._fadeInWithMasterGainNode(duration);
5810 //
5811 } // _fadeIn
5812
5813 static _fadeOut(duration) {
5814 // Edited to help plugins fade out with master gain node in better ways
5815 if (this._masterGainNode) this._fadeOutWithMasterGainNode(duration);
5816 //
5817 } // _fadeOut
5818
5819 /**
5820 * Clears the audio data.
5821 */
5822 clear() {
5823 this.stop();
5824 this._data = this._buffer = null;
5825 this._sourceNode = this._gainNode = this._pannerNode = null;
5826 this._totalTime = this._sampleRate = 0;
5827 this._loop = this._loopStart = this._loopLength = 0;
5828 this._loopStartTime = this._loopLengthTime = this._startTime = 0;
5829 this._volume = this._pitch = 1;
5830 [this._pan, this._endTimer] = [0, null];
5831 [this._loadListeners, this._stopListeners] = [[], []];
5832 this._lastUpdateTime = 0;
5833 this._isLoaded = this._isError = this._isPlaying = false;
5834 this._decoder = null;
5835 } // clear
5836
5837 /**
5838 * The url of the audio file.
5839 *
5840 * @readonly
5841 * @type string
5842 * @name WebAudio#url
5843 */
5844 get url() { return this._url; }
5845
5846 /**
5847 * The volume of the audio.
5848 *
5849 * @type number
5850 * @name WebAudio#volume
5851 */
5852 get volume() { return this._volume; }
5853 set volume(value) {
5854 this._volume = value;
5855 if (!this._gainNode) return;
5856 this._gainNode.gain.setValueAtTime(value, WebAudio._currentTime());
5857 } // volume
5858
5859 /**
5860 * The pitch of the audio.
5861 *
5862 * @type number
5863 * @name WebAudio#pitch
5864 */
5865 get pitch() { return this._pitch; }
5866 set pitch(value) {
5867 if (this._pitch === value) return;
5868 this._pitch = value;
5869 if (this.isPlaying()) this.play(this._loop, 0);
5870 } // pitch
5871
5872 /**
5873 * The pan of the audio.
5874 *
5875 * @type number
5876 * @name WebAudio#pan
5877 */
5878 get pan() { return this._pan; }
5879 set pan(value) {
5880 this._pan = value;
5881 this._updatePanner();
5882 } // pan
5883
5884 /**
5885 * Checks whether the audio data is ready to play.
5886 *
5887 * @returns {boolean} True if the audio data is ready to play.
5888 */
5889 isReady() { return !!this._buffer; }
5890
5891 /**
5892 * Checks whether a loading error has occurred.
5893 *
5894 * @returns {boolean} True if a loading error has occurred.
5895 */
5896 isError() { return this._isError; }
5897
5898 /**
5899 * Checks whether the audio is playing.
5900 *
5901 * @returns {boolean} True if the audio is playing.
5902 */
5903 isPlaying() { return this._isPlaying; }
5904
5905 /**
5906 * Plays the audio.
5907 *
5908 * @param {boolean} loop - Whether the audio data play in a loop.
5909 * @param {number} offset - The start position to play in seconds.
5910 */
5911 play(loop, offset) {
5912 this._loop = loop;
5913 if (this.isReady()) {
5914 this._startPlaying(offset || 0);
5915 } else if (WebAudio._context) {
5916 this.addLoadListener(this.play.bind(this, loop, offset));
5917 }
5918 this._isPlaying = true;
5919 } // play
5920
5921 /**
5922 * Stops the audio.
5923 */
5924 stop() {
5925 this._isPlaying = false;
5926 this._removeEndTimer();
5927 this._removeNodes();
5928 this._loadListeners = [];
5929 // Edited to help plugins call stop listeners in better ways
5930 if (this._stopListeners) this._callStopListeners();
5931 //
5932 } // stop
5933
5934 /**
5935 * Destroys the audio.
5936 */
5937 destroy() {
5938 this.stop();
5939 this._destroyDecoder();
5940 } // destroy
5941
5942 /**
5943 * Performs the audio fade-in.
5944 *
5945 * @param {number} duration - Fade-in time in seconds.
5946 */
5947 fadeIn(duration) {
5948 // Edited to help plugins alter fade in ready behaviors in better ways
5949 if (this.isReady()) return this._fadeInWhenReady(duration);
5950 //
5951 this.addLoadListener(this.fadeIn.bind(this, duration));
5952 } // fadeIn
5953
5954 /**
5955 * Performs the audio fade-out.
5956 *
5957 * @param {number} duration - Fade-out time in seconds.
5958 */
5959 fadeOut(duration) {
5960 // Edited to help plugins alter fade out with gain node in better ways
5961 if (this._gainNode) this._fadeOutWithGainNode(duration);
5962 //
5963 this._isPlaying = false;
5964 this._loadListeners = [];
5965 } // fadeOut
5966
5967 /**
5968 * Gets the seek position of the audio.
5969 */
5970 // Edited to help plugins alter seeking with context in better ways
5971 seek() { return WebAudio._context ? this._seekWithContext() : 0; }
5972 //
5973
5974 /**
5975 * Adds a callback function that will be called when the audio data is loaded.
5976 *
5977 * @param {function} listner - The callback function.
5978 */
5979 addLoadListener(listner) { this._loadListeners.push(listner); }
5980
5981 /**
5982 * Adds a callback function that will be called when the playback is stopped.
5983 *
5984 * @param {function} listner - The callback function.
5985 */
5986 addStopListener(listner) { this._stopListeners.push(listner); }
5987
5988 /**
5989 * Tries to load the audio again.
5990 */
5991 retry() {
5992 this._startLoading();
5993 if (this._isPlaying) this.play(this._loop, 0);
5994 } // retry
5995
5996 // Edited to help plugins alter start loading behaviors in better ways
5997 _startLoading() { if (WebAudio._context) this._startLoadingWithContext(); }
5998 //
5999
6000 _shouldUseDecoder() {
6001 return !Utils.canPlayOgg() && typeof VorbisDecoder === "function";
6002 } // _shouldUseDecoder
6003
6004 _createDecoder() {
6005 this._decoder = new VorbisDecoder(
6006 WebAudio._context,
6007 this._onDecode.bind(this),
6008 this._onError.bind(this)
6009 );
6010 } // _createDecoder
6011
6012 _destroyDecoder() {
6013 if (!this._decoder) return;
6014 this._decoder.destroy();
6015 this._decoder = null;
6016 } // _destroyDecoder
6017
6018 _realUrl() { return `${this._url}${Utils.hasEncryptedAudio() ? "_" : ""}`; }
6019
6020 // Edited to help plugins alter start loading xhr behaviors in better ways
6021 _startXhrLoading(url) { this._newLoadingXhr(url).sned(); }
6022 //
6023
6024 _startFetching(url) {
6025 fetch(url).then(this._onFetch.bind(this)).catch(this._onError.bind(this));
6026 } // _startFetching
6027
6028 _onXhrLoad(xhr) {
6029 // Edited to help pluggins alter the xhr load behaviors in better ways
6030 xhr.status < 400 ? this._onXhrLoadSuc(xhr) : this._onError();
6031 //
6032 } // _onXhrLoad
6033
6034 _onFetch(response) {
6035 // Edited to help plugins alter on fetch ok behaviors in better ways
6036 response.ok ? this._onFetchOk(response) : this._onError();
6037 //
6038 } // _onFetch
6039
6040 _onError() {
6041 if (this._sourceNode) this._stopSourceNode();
6042 [this._data, this._isError] = [null, true];
6043 } // _onError
6044
6045 _onFetchProcess(value) {
6046 const currentData = this._data;
6047 const currentSize = currentData ? currentData.length : 0;
6048 const newData = new Uint8Array(currentSize + value.length);
6049 if (currentData) newData.set(currentData);
6050 newData.set(value, currentSize);
6051 this._data = newData;
6052 this._updateBufferOnFetch();
6053 } // _onFetchProcess
6054
6055 _updateBufferOnFetch() {
6056 // [Note] Too frequent updates have a negative impact on sound quality.
6057 // In addition, decodeAudioData() may fail if the data is being fetched
6058 // and is too small.
6059 const currentTime = performance.now();
6060 const deltaTime = currentTime - this._lastUpdateTime;
6061 /** @todo Extracts these codes into well-named functions */
6062 if (deltaTime >= 500 && this._data.length >= 50000) {
6063 this._updateBuffer();
6064 this._lastUpdateTime = currentTime;
6065 }
6066 //
6067 } // _updateBufferOnFetch
6068
6069 _updateBuffer() {
6070 const arrayBuffer = this._readableBuffer();
6071 this._readLoopComments(arrayBuffer);
6072 this._decodeAudioData(arrayBuffer);
6073 } // _updateBuffer
6074
6075 _readableBuffer() {
6076 if (!Utils.hasEncryptedAudio()) return this._data.buffer;
6077 return Utils.decryptArrayBuffer(this._data.buffer);
6078 } // _readableBuffer
6079
6080 _decodeAudioData(arrayBuffer) {
6081 if (this._shouldUseDecoder()) {
6082 /** @todo Extracts these codes into a well-named function */
6083 if (this._decoder) this._decoder.send(arrayBuffer, this._isLoaded);
6084 //
6085 } else {
6086 // [Note] Make a temporary copy of arrayBuffer because
6087 // decodeAudioData() detaches it.
6088 WebAudio._context
6089 .decodeAudioData(arrayBuffer.slice())
6090 .then(this._onDecode.bind(this))
6091 .catch(this._onError.bind(this));
6092 }
6093 } // _decodeAudioData
6094
6095 _onDecode(buffer) {
6096 [this._buffer, this._totalTime] = [buffer, buffer.duration];
6097 /** @todo Extracts these codes into well-named functions */
6098 if (this._loopLength > 0 && this._sampleRate > 0) {
6099 this._loopStartTime = this._loopStart / this._sampleRate;
6100 this._loopLengthTime = this._loopLength / this._sampleRate;
6101 } else {
6102 this._loopStartTime = 0;
6103 this._loopLengthTime = this._totalTime;
6104 }
6105 //
6106 if (this._sourceNode) this._refreshSourceNode();
6107 this._onLoad();
6108 } // _onDecode
6109
6110 _refreshSourceNode() {
6111 this._stopSourceNode();
6112 this._createSourceNode();
6113 // Edited to help plugins alter refresh source node in better ways
6114 if (this._isPlaying) this._refreshSourceNodeWhenPlaying();
6115 //
6116 } // _refreshSourceNode
6117
6118 _startPlaying(offset) {
6119 // Edited to help plugins alter the start playing offset in better ways
6120 const playingOffset = this._startPlayingOffset(offset);
6121 //
6122 this._removeEndTimer();
6123 this._removeNodes();
6124 this._createPannerNode();
6125 this._createGainNode();
6126 this._createSourceNode();
6127 this._startSourceNode(0, playingOffset);
6128 this._startTime = WebAudio._currentTime() - playingOffset / this._pitch;
6129 this._createEndTimer();
6130 } // _startPlaying
6131
6132 _startSourceNode(when, offset) {
6133 if (offset >= this._buffer.duration) return;
6134 this._sourceNode.start(when, offset);
6135 } // _startSourceNode
6136
6137 // Ignore InvalidStateError
6138 _stopSourceNode() { try { this._sourceNode.stop(); } catch (e) {} }
6139
6140 _createPannerNode() {
6141 this._pannerNode = WebAudio._context.createPanner();
6142 this._pannerNode.panningModel = "equalpower";
6143 this._pannerNode.connect(WebAudio._masterGainNode);
6144 this._updatePanner();
6145 } // _createPannerNode
6146
6147 _createGainNode() {
6148 const currentTime = WebAudio._currentTime();
6149 this._gainNode = WebAudio._context.createGain();
6150 this._gainNode.gain.setValueAtTime(this._volume, currentTime);
6151 this._gainNode.connect(this._pannerNode);
6152 } // _createGainNode
6153
6154 _createSourceNode() {
6155 const currentTime = WebAudio._currentTime();
6156 this._sourceNode = WebAudio._context.createBufferSource();
6157 this._sourceNode.buffer = this._buffer;
6158 this._sourceNode.loop = this._loop && this._isLoaded;
6159 this._sourceNode.loopStart = this._loopStartTime;
6160 this._sourceNode.loopEnd = this._loopStartTime + this._loopLengthTime;
6161 this._sourceNode.playbackRate.setValueAtTime(this._pitch, currentTime);
6162 this._sourceNode.connect(this._gainNode);
6163 } // _createSourceNode
6164
6165 // Edited to help plugins alter the remove nodes behaviors in better way
6166 _removeNodes() { if (this._sourceNode) this._removeNodesWithSourceNode(); }
6167
6168 _createEndTimer() {
6169 /** @todo Extracts these codes into well-named functions */
6170 if (this._sourceNode && !this._loop) {
6171 const endTime = this._startTime + this._totalTime / this._pitch;
6172 const delay = endTime - WebAudio._currentTime();
6173 this._endTimer = setTimeout(this.stop.bind(this), delay * 1000);
6174 }
6175 //
6176 } // _createEndTimer
6177
6178 _removeEndTimer() {
6179 if (!this._endTimer) return;
6180 clearTimeout(this._endTimer);
6181 this._endTimer = null;
6182 } // _removeEndTimer
6183
6184 _updatePanner() {
6185 if (!this._pannerNode) return;
6186 this._pannerNode.setPosition(this._pan, 0, 1 - Math.abs(this._pan));
6187 } // _updatePanner
6188
6189 _onLoad() {
6190 while (!this._loadListeners.isEmpty()) this._loadListeners.shift()();
6191 } // _onLoad
6192
6193 _readLoopComments(arrayBuffer) {
6194 const view = new DataView(arrayBuffer), maxI = view.byteLength - 30;
6195 let index = 0;
6196 while (index < maxI) {
6197 if (this._readFourCharacters(view, index) !== "OggS") break;
6198 index += 26;
6199 const [numSegments, segments] = [view.getUint8(index++), []];
6200 for (let i = 0; i < numSegments; i++) {
6201 segments.push(view.getUint8(index++));
6202 }
6203 // Edited to help plugins alter loop comment packets in better ways
6204 const packets = this._loopCommentPackets(segments);
6205 //
6206 let vorbisHeaderFound = false;
6207 packets.forEach(size => {
6208 if (this._readFourCharacters(view, index + 1) === "vorb") {
6209 // Edited to help plugins alter read packet in better ways
6210 this._readLoopCommentPacket(view, index, size);
6211 //
6212 vorbisHeaderFound = true;
6213 }
6214 index += size;
6215 });
6216 if (!vorbisHeaderFound) break;
6217 }
6218 } // _readLoopComments
6219
6220 _readMetaData(view, index, size) {
6221 const maxI = index + size - 10;
6222 for (let i = index; i < maxI; i++) {
6223 if (this._readFourCharacters(view, i) !== "LOOP") continue;
6224 let text = "";
6225 while (view.getUint8(i) > 0) {
6226 text += String.fromCharCode(view.getUint8(i++));
6227 }
6228 // Edited to help plugins alter loop start/length in better ways
6229 this._readMetaLoopStartLength(text);
6230 //
6231 if (text === "LOOPSTART" || text === "LOOPLENGTH") {
6232 let text2 = "";
6233 i += 16;
6234 while (view.getUint8(i) > 0) {
6235 text2 += String.fromCharCode(view.getUint8(i++));
6236 }
6237 if (text === "LOOPSTART") {
6238 this._loopStart = parseInt(text2);
6239 } else this._loopLength = parseInt(text2);
6240 }
6241 }
6242 } // _readMetaData
6243
6244 _readFourCharacters(view, index) {
6245 let string = "";
6246 if (index <= view.byteLength - 4) {
6247 for (let i = 0; i < 4; i++) {
6248 string += String.fromCharCode(view.getUint8(index + i));
6249 }
6250 }
6251 return string;
6252 } // _readFourCharacters
6253
6254 /**
6255 * Creates the master gain node with the global web audio context
6256 * Idempotent
6257 * @author DoubleX @since 0.9.5 @version 0.9.5
6258 */
6259 static _createMasterGainNodeWithContext() {
6260 this._masterGainNode = this._context.createGain();
6261 this._resetVolume();
6262 this._masterGainNode.connect(this._context.destination);
6263 } // _createMasterGainNodeWithContext
6264
6265 /**
6266 * Resets the master volume with its master gain node being present
6267 * Idempotent
6268 * @author DoubleX @since 0.9.5 @version 0.9.5
6269 */
6270 static _resetVolWithMasterGainNode() {
6271 const [gain, volume] = [this._masterGainNode.gain, this._masterVolume];
6272 const currentTime = this._currentTime();
6273 gain.setValueAtTime(volume, currentTime);
6274 } // _resetVolWithMasterGainNode
6275
6276 /**
6277 * Fades in the global web audio with its master gain node being present
6278 * Idempotent
6279 * @author DoubleX @since 0.9.5 @version 0.9.5
6280 * @param {number} duration - Fade-out time in seconds
6281 */
6282 static _fadeInWithMasterGainNode(duration) {
6283 const [gain, volume] = [this._masterGainNode.gain, this._masterVolume];
6284 const currentTime = this._currentTime();
6285 /** @todo Dries up these codes representing identical knowledge */
6286 gain.setValueAtTime(0, currentTime);
6287 gain.linearRampToValueAtTime(volume, currentTime + duration);
6288 //
6289 } // _fadeInWithMasterGainNode
6290
6291 /**
6292 * Fades out the global web audio with its master gain node being present
6293 * Idempotent
6294 * @author DoubleX @since 0.9.5 @version 0.9.5
6295 * @param {number} duration - Fade-out time in seconds
6296 */
6297 static _fadeOutWithMasterGainNode(duration) {
6298 const [gain, volume] = [this._masterGainNode.gain, this._masterVolume];
6299 const currentTime = this._currentTime();
6300 /** @todo Dries up these codes representing identical knowledge */
6301 gain.setValueAtTime(volume, currentTime);
6302 gain.linearRampToValueAtTime(0, currentTime + duration);
6303 //
6304 } // _fadeOutWithMasterGainNode
6305
6306 /**
6307 * Calls all stop listeners when this web audio's just stopped
6308 * Idempotent
6309 * @author DoubleX @since 0.9.5 @version 0.9.5
6310 * @param {number} duration - Fade-out time in seconds
6311 */
6312 _callStopListeners() {
6313 while (!this._stopListeners.isEmpty()) this._stopListeners.shift()();
6314 } // _callStopListeners
6315
6316 /**
6317 * Fades in this web audio when it's ready
6318 * Idempotent
6319 * @author DoubleX @since 0.9.5 @version 0.9.5
6320 * @param {number} duration - Fade-out time in seconds
6321 */
6322 _fadeInWhenReady(duration) {
6323 if (this._gainNode) this._fadeInWhenReadyWithGainNode(duration);
6324 } // _fadeInWhenReady
6325
6326 /**
6327 * Fades in this web audio when it's ready with its gain node being present
6328 * Idempotent
6329 * @author DoubleX @since 0.9.5 @version 0.9.5
6330 * @param {number} duration - Fade-out time in seconds
6331 */
6332 _fadeInWhenReadyWithGainNode(duration) {
6333 const gain = this._gainNode.gain;
6334 const currentTime = WebAudio._currentTime();
6335 /** @todo Dries up these codes representing identical knowledge */
6336 gain.setValueAtTime(0, currentTime);
6337 gain.linearRampToValueAtTime(this._volume, currentTime + duration);
6338 //
6339 } // _fadeInWhenReadyWithGainNode
6340
6341 /**
6342 * Fades out this web audio with its gain node being present
6343 * Idempotent
6344 * @author DoubleX @since 0.9.5 @version 0.9.5
6345 * @param {number} duration - Fade-out time in seconds
6346 */
6347 _fadeOutWithGainNode(duration) {
6348 const gain = this._gainNode.gain;
6349 const currentTime = WebAudio._currentTime();
6350 /** @todo Dries up these codes representing identical knowledge */
6351 gain.setValueAtTime(this._volume, currentTime);
6352 gain.linearRampToValueAtTime(0, currentTime + duration);
6353 //
6354 } // _fadeOutWithGainNode
6355
6356 /**
6357 * Nullipotent
6358 * @author DoubleX @since 0.9.5 @version 0.9.5
6359 * @returns {number} The seek position of this web audio with global context
6360 */
6361 _seekWithContext() {
6362 let pos = (WebAudio._currentTime() - this._startTime) * this._pitch;
6363 if (this._loopLength > 0) {
6364 const seekPos = this._loopStart + this._loopLength;
6365 while (pos >= seekPos) pos -= this._loopLength;
6366 }
6367 return pos;
6368 } // _seekWithContext
6369
6370 /**
6371 * Starts loading this web audio data with the global web audio context
6372 * Idempotent
6373 * @author DoubleX @since 0.9.5 @version 0.9.5
6374 */
6375 _startLoadingWithContext() {
6376 const url = this._realUrl();
6377 Utils.isLocal() ? this._startXhrLoading(url) : this._startFetching(url);
6378 this._lastUpdateTime = -10000;
6379 this._isError = this._isLoaded = false;
6380 this._destroyDecoder();
6381 if (this._shouldUseDecoder()) this._createDecoder();
6382 } // _startLoadingWithContext
6383
6384 /**
6385 * Nullipotent
6386 * @author DoubleX @since 0.9.5 @version 0.9.5
6387 * @returns {XMLHttpRequest} The GET XMLHttpRequest receiving array buffers
6388 */
6389 _newLoadingXhr(url) {
6390 const xhr = new XMLHttpRequest();
6391 xhr.open("GET", url);
6392 xhr.responseType = "arraybuffer";
6393 xhr.onload = this._onXhrLoad.bind(this, xhr);
6394 xhr.onerror = this._onError.bind(this);
6395 return xhr;
6396 } // _newLoadingXhr
6397
6398 /**
6399 * Loads the data and updates the buffer of this local web audio
6400 * Idempotent
6401 * @author DoubleX @since 0.9.5 @version 0.9.5
6402 * @param {XMLHttpRequest} xhr - The GET XMLHttpRequest with array buffers
6403 */
6404 _onXhrLoadSuc(xhr) {
6405 [this._data, this._isLoaded] = [new Uint8Array(xhr.response), true];
6406 this._updateBuffer();
6407 } // _onXhrLoadSuc
6408
6409 /**
6410 * Triggers the events when the non-local web audio fetches' okay
6411 * Idempotent
6412 * @author DoubleX @since 0.9.5 @version 0.9.5
6413 * @param {Response} response - The non-local web audio fetch okay response
6414 */
6415 _onFetchOk(response) {
6416 const reader = response.body.getReader();
6417 /** @todo Extracts this into a method without obscuring the recursion */
6418 const readChunk = ({ done, value }) => {
6419 if (done) {
6420 this._isLoaded = true;
6421 this._updateBuffer();
6422 return 0;
6423 } else {
6424 this._onFetchProcess(value);
6425 return reader.read().then(readChunk);
6426 }
6427 };
6428 //
6429 reader.read().then(readChunk).catch(this._onError.bind(this));
6430 } // _onFetchOk
6431
6432 /**
6433 * Refreshes the source node of this web audio when it's playing
6434 * Idempotent
6435 * @author DoubleX @since 0.9.5 @version 0.9.5
6436 */
6437 _refreshSourceNodeWhenPlaying() {
6438 const currentTime = WebAudio._currentTime();
6439 this._startSourceNode(0, (currentTime - this._startTime) * this._pitch);
6440 this._removeEndTimer();
6441 this._createEndTimer();
6442 } // _refreshSourceNodeWhenPlaying
6443
6444 /**
6445 * Nullipotent
6446 * @author DoubleX @since 0.9.5 @version 0.9.5
6447 * @param {number} offset - The raw start playing offset of this web audio
6448 * @returns {number} The corrected start playing offset of this web audio
6449 */
6450 _startPlayingOffset(offset) {
6451 if (this._loopLengthTime <= 0) return offset;
6452 const loopEnd = this._loopStartTime + this._loopLengthTime;
6453 let playingOffset = offset;
6454 while (playingOffset >= loopEnd) playingOffset -= this._loopLengthTime;
6455 return playingOffset;
6456 } // _startPlayingOffset
6457
6458 /**
6459 * This method shouldn't be called without an existing source node
6460 * Idempotent
6461 * @author DoubleX @since 0.9.5 @version 0.9.5
6462 */
6463 _removeNodesWithSourceNode() {
6464 this._stopSourceNode();
6465 this._sourceNode = this._gainNode = this._pannerNode = null;
6466 } // _removeNodesWithSourceNode
6467
6468 /**
6469 * Reads the loop comment from a packet in the packet list of this web audio
6470 * Idempotent
6471 * @author DoubleX @since 0.9.5 @version 0.9.5
6472 * @param {[uint8]} segments - The list of 8 bit integers in array buffer
6473 * @returns {[number]} The list of the sizes of the loop comment packets
6474 */
6475 _loopCommentPackets(segments) {
6476 const packets = [];
6477 while (!segments.isEmpty()) {
6478 let packetSize = 0;
6479 while (segments[0] === 255) packetSize += segments.shift();
6480 packetSize += segments.shift();
6481 packets.push(packetSize);
6482 }
6483 return packets;
6484 } // _loopCommentPackets
6485
6486 /**
6487 * Reads the loop comment from a packet in the packet list of this web audio
6488 * Idempotent
6489 * @author DoubleX @since 0.9.5 @version 0.9.5
6490 * @param {DataView} view - The data view of the loop comment array buffer
6491 * @param {index} i - The index of the current byte in the data array buffer
6492 * @param {number} size - The size of this packet to be read
6493 */
6494 _readLoopCommentPacket(view, i, size) {
6495 const headerType = view.getUint8(i);
6496 if (headerType === 1) {
6497 this._sampleRate = view.getUint32(i + 12, true);
6498 } else if (headerType === 3) this._readMetaData(view, i, size);
6499 } // _readLoopCommentPacket
6500
6501 /**
6502 * Reads the loop start and length meta data for this web audio
6503 * Idempotent
6504 * @author DoubleX @since 0.9.5 @version 0.9.5
6505 * @param {string} text - The meta data text having the loop start/length
6506 */
6507 _readMetaLoopStartLength(text) {
6508 if (text.match(/LOOPSTART=([0-9]+)/)) {
6509 this._loopStart = parseInt(RegExp.$1);
6510 }
6511 if (!text.match(/LOOPLENGTH=([0-9]+)/)) return;
6512 this._loopLength = parseInt(RegExp.$1);
6513 } // _readMetaLoopStartLength
6514
6515} // WebAudio
6516
6517/*----------------------------------------------------------------------------
6518 * # Rewritten class: Window
6519 * - Rewrites it into the ES6 standard
6520 *----------------------------------------------------------------------------*/
6521
6522//-----------------------------------------------------------------------------
6523/**
6524 * The window in the game.
6525 *
6526 * @class
6527 * @extends PIXI.Container
6528 */
6529class Window extends PIXI.Container {
6530
6531 constructor() {
6532 super();
6533 this._initPrivateVars();
6534 this._createAllParts();
6535 this._initPublicVars();
6536 } // constructor
6537 /**
6538 * The image used as a window skin.
6539 *
6540 * @type Bitmap
6541 * @name Window#windowskin
6542 */
6543 get windowskin() { return this._windowskin; }
6544 set windowskin(value) {
6545 if (this._windowskin === value) return;
6546 this._windowskin = value;
6547 this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
6548 } // windowskin
6549
6550 /**
6551 * The bitmap used for the window contents.
6552 *
6553 * @type Bitmap
6554 * @name Window#contents
6555 */
6556 get contents() { return this._contentsSprite.bitmap; }
6557 set contents(value) { this._contentsSprite.bitmap = value; }
6558
6559 /**
6560 * The bitmap used for the window contents background.
6561 *
6562 * @type Bitmap
6563 * @name Window#contentsBack
6564 */
6565 get contentsBack() { return this._contentsBackSprite.bitmap; }
6566 set contentsBack(value) { this._contentsBackSprite.bitmap = value; }
6567
6568 /**
6569 * The width of the window in pixels.
6570 *
6571 * @type number
6572 * @name Window#width
6573 */
6574 get width() { return this._width; }
6575 set width(value) {
6576 /** @todo Checks whether refresh's needed if the width's unchanged */
6577 this._width = value;
6578 this._refreshAllParts();
6579 //
6580 } // width
6581
6582 /**
6583 * The height of the window in pixels.
6584 *
6585 * @type number
6586 * @name Window#height
6587 */
6588 get height() { return this._height; }
6589 set height(value) {
6590 /** @todo Checks whether refresh's needed if the height's unchanged */
6591 this._height = value;
6592 this._refreshAllParts();
6593 //
6594 } // height
6595
6596 /**
6597 * The size of the padding between the frame and contents.
6598 *
6599 * @type number
6600 * @name Window#padding
6601 */
6602 get padding() { return this._padding; }
6603 set padding(value) {
6604 /** @todo Checks whether refresh's needed if the padding's unchanged */
6605 this._padding = value;
6606 this._refreshAllParts();
6607 //
6608 } // padding
6609
6610 /**
6611 * The size of the margin for the window background.
6612 *
6613 * @type number
6614 * @name Window#margin
6615 */
6616 get margin() { return this._margin; }
6617 set margin(value) {
6618 /** @todo Checks whether refresh's needed if the margin's unchanged */
6619 this._margin = value;
6620 this._refreshAllParts();
6621 //
6622 } // margin
6623
6624 /**
6625 * The opacity of the window without contents (0 to 255).
6626 *
6627 * @type number
6628 * @name Window#opacity
6629 */
6630 get opacity() { return this._container.alpha * 255; }
6631 set opacity(value) { this._container.alpha = value.clamp(0, 255) / 255; }
6632
6633 /**
6634 * The opacity of the window background (0 to 255).
6635 *
6636 * @type number
6637 * @name Window#backOpacity
6638 */
6639 get backOpacity() { return this._backSprite.alpha * 255; }
6640 set backOpacity(value) {
6641 this._backSprite.alpha = value.clamp(0, 255) / 255;
6642 } // backOpacity
6643
6644 /**
6645 * The opacity of the window contents (0 to 255).
6646 *
6647 * @type number
6648 * @name Window#contentsOpacity
6649 */
6650 get contentsOpacity() { return this._contentsSprite.alpha * 255; }
6651 set contentsOpacity(value) {
6652 this._contentsSprite.alpha = value.clamp(0, 255) / 255;
6653 } // contentsOpacity
6654
6655 /**
6656 * The openness of the window (0 to 255).
6657 *
6658 * @type number
6659 * @name Window#openness
6660 */
6661 get openness() { return this._openness; }
6662 set openness(value) {
6663 if (this._openness === value) return;
6664 this._openness = value.clamp(0, 255);
6665 this._container.scale.y = this._openness / 255;
6666 this._container.y = (this.height / 2) * (1 - this._openness / 255);
6667 } // openness
6668
6669 /**
6670 * The width of the content area in pixels.
6671 *
6672 * @readonly
6673 * @type number
6674 * @name Window#innerWidth
6675 */
6676 get innerWidth() { return Math.max(0, this._width - this._padding * 2); }
6677
6678 /**
6679 * The height of the content area in pixels.
6680 *
6681 * @readonly
6682 * @type number
6683 * @name Window#innerHeight
6684 */
6685 get innerHeight() { return Math.max(0, this._height - this._padding * 2); }
6686
6687 /**
6688 * The rectangle of the content area.
6689 *
6690 * @readonly
6691 * @type Rectangle
6692 * @name Window#innerRect
6693 */
6694 get innerRect() {
6695 return new Rectangle(
6696 this._padding,
6697 this._padding,
6698 this.innerWidth,
6699 this.innerHeight
6700 );
6701 } // innerRect
6702
6703 /**
6704 * Destroys the window.
6705 */
6706 destroy() { super.destroy({ children: true, texture: true }); }
6707
6708 /**
6709 * Updates the window for each frame.
6710 */
6711 update() {
6712 if (this.active) this._animationCount++;
6713 this.children.forEach(child => { if (child.update) child.update(); });
6714 } // update
6715
6716 /**
6717 * Sets the x, y, width, and height all at once.
6718 *
6719 * @param {number} x - The x coordinate of the window.
6720 * @param {number} y - The y coordinate of the window.
6721 * @param {number} width - The width of the window.
6722 * @param {number} height - The height of the window.
6723 */
6724 move(x = 0, y = 0, width = 0, height = 0) {
6725 // They shouldn't be "", false, null, NaN or other defined falsy values
6726 [this.x, this.y] = [x, y];
6727 if (this._width === width && this._height === height) return;
6728 //
6729 [this._width, this._height] = [width, height];
6730 this._refreshAllParts();
6731 } // move
6732
6733 /**
6734 * Checks whether the window is completely open (openness == 255).
6735 *
6736 * @returns {boolean} True if the window is open.
6737 */
6738 isOpen() { return this._openness >= 255; }
6739
6740 /**
6741 * Checks whether the window is completely closed (openness == 0).
6742 *
6743 * @returns {boolean} True if the window is closed.
6744 */
6745 isClosed() { return this._openness <= 0; }
6746
6747 /**
6748 * Sets the position of the command cursor.
6749 *
6750 * @param {number} x - The x coordinate of the cursor.
6751 * @param {number} y - The y coordinate of the cursor.
6752 * @param {number} width - The width of the cursor.
6753 * @param {number} height - The height of the cursor.
6754 */
6755 setCursorRect(x = 0, y = 0, width = 0, height = 0) {
6756 // They shouldn't be "", false, null, NaN or other defined falsy values
6757 const [cw, ch] = [Math.floor(width), Math.floor(height)];
6758 this._cursorRect.x = Math.floor(x);
6759 this._cursorRect.y = Math.floor(y);
6760 //
6761 if (this._cursorRect.width !== cw || this._cursorRect.height !== ch) {
6762 // Edited to help plugins alter cursor width height in better ways
6763 this._setNewCursorWH(cw, ch);
6764 //
6765 }
6766 } // setCursorRect
6767
6768 /**
6769 * Moves the cursor position by the given amount.
6770 *
6771 * @param {number} x - The amount of horizontal movement.
6772 * @param {number} y - The amount of vertical movement.
6773 */
6774 moveCursorBy(x, y) { this._cursorRect.x += x, this._cursorRect.y += y; }
6775
6776 /**
6777 * Moves the inner children by the given amount.
6778 *
6779 * @param {number} x - The amount of horizontal movement.
6780 * @param {number} y - The amount of vertical movement.
6781 */
6782 moveInnerChildrenBy(x, y) {
6783 this._innerChildren.forEach(child => { child.x += x, child.y += y; });
6784 } // moveInnerChildrenBy
6785
6786 /**
6787 * Changes the color of the background.
6788 *
6789 * @param {number} r - The red value in the range (-255, 255).
6790 * @param {number} g - The green value in the range (-255, 255).
6791 * @param {number} b - The blue value in the range (-255, 255).
6792 */
6793 setTone(r, g, b) {
6794 const tone = this._colorTone;
6795 if (r === tone[0] && g === tone[1] && b === tone[2]) return;
6796 this._colorTone = [r, g, b, 0];
6797 this._refreshBack();
6798 } // setTone
6799
6800 /**
6801 * Adds a child between the background and contents.
6802 *
6803 * @param {object} child - The child to add.
6804 * @returns {object} The child that was added.
6805 */
6806 addChildToBack(child) {
6807 const containerIndex = this.children.indexOf(this._container);
6808 return this.addChildAt(child, containerIndex + 1);
6809 } // addChildToBack
6810
6811 /**
6812 * Adds a child to the client area.
6813 *
6814 * @param {object} child - The child to add.
6815 * @returns {object} The child that was added.
6816 */
6817 addInnerChild(child) {
6818 this._innerChildren.push(child);
6819 return this._clientArea.addChild(child);
6820 } // addInnerChild
6821
6822 /**
6823 * Updates the transform on all children of this container for rendering.
6824 */
6825 updateTransform() {
6826 this._updateClientArea();
6827 this._updateFrame();
6828 this._updateContentsBack();
6829 this._updateCursor();
6830 this._updateContents();
6831 this._updateArrows();
6832 this._updatePauseSign();
6833 super.updateTransform();
6834 this._updateFilterArea();
6835 } // updateTransform
6836
6837 /**
6838 * Draws the window shape into PIXI.Graphics object. Used by WindowLayer.
6839 */
6840 drawShape(graphics) {
6841 // Edited to help plugins alter draw shape behaviors in better ways
6842 if (graphics) this._drawShapeWithGraphics(graphics);
6843 //
6844 } // drawShape
6845
6846 _createAllParts() {
6847 this._createContainer();
6848 this._createBackSprite();
6849 this._createFrameSprite();
6850 this._createClientArea();
6851 this._createContentsBackSprite();
6852 this._createCursorSprite();
6853 this._createContentsSprite();
6854 this._createArrowSprites();
6855 this._createPauseSignSprites();
6856 } // _createAllParts
6857
6858 _createContainer() {
6859 this._container = new PIXI.Container();
6860 this.addChild(this._container);
6861 } // _createContainer
6862
6863 _createBackSprite() {
6864 this._backSprite = new Sprite();
6865 this._backSprite.addChild(new TilingSprite());
6866 this._container.addChild(this._backSprite);
6867 } // _createBackSprite
6868
6869 _createFrameSprite() {
6870 this._frameSprite = new Sprite();
6871 for (let i = 0; i < 8; i++) {
6872 this._frameSprite.addChild(new Sprite());
6873 }
6874 this._container.addChild(this._frameSprite);
6875 } // _createFrameSprite
6876
6877 _createClientArea() {
6878 // Edited to help plugins alter create client area in better ways
6879 this._clientArea = this._newClientArea();
6880 //
6881 this.addChild(this._clientArea);
6882 } // _createClientArea
6883
6884 _createContentsBackSprite() {
6885 this._contentsBackSprite = new Sprite();
6886 this._clientArea.addChild(this._contentsBackSprite);
6887 } // _createContentsBackSprite
6888
6889 _createCursorSprite() {
6890 this._cursorSprite = new Sprite();
6891 for (let i = 0; i < 9; i++) {
6892 this._cursorSprite.addChild(new Sprite());
6893 }
6894 this._clientArea.addChild(this._cursorSprite);
6895 } // _createCursorSprite
6896
6897 _createContentsSprite() {
6898 this._contentsSprite = new Sprite();
6899 this._clientArea.addChild(this._contentsSprite);
6900 } // _createContentsSprite
6901
6902 _createArrowSprites() {
6903 this._downArrowSprite = new Sprite();
6904 this.addChild(this._downArrowSprite);
6905 this._upArrowSprite = new Sprite();
6906 this.addChild(this._upArrowSprite);
6907 } // _createArrowSprites
6908
6909 _createPauseSignSprites() {
6910 this._pauseSignSprite = new Sprite();
6911 this.addChild(this._pauseSignSprite);
6912 } // _createPauseSignSprites
6913
6914 _onWindowskinLoad() { this._refreshAllParts(); }
6915
6916 _refreshAllParts() {
6917 this._refreshBack();
6918 this._refreshFrame();
6919 this._refreshCursor();
6920 this._refreshArrows();
6921 this._refreshPauseSign();
6922 } // _refreshAllParts
6923
6924 _refreshBack() {
6925 const w = Math.max(0, this._width - this._margin * 2);
6926 const h = Math.max(0, this._height - this._margin * 2);
6927 // Edited to help plugins alter refresh back behaviors in better ways
6928 this._refreshBackSprite(w, h);
6929 this._refreshBackTilingSprite(w, h);
6930 //
6931 } // refreshBack
6932
6933 _refreshFrame() {
6934 const drect = { x: 0, y: 0, width: this._width, height: this._height };
6935 const srect = { x: 96, y: 0, width: 96, height: 96 };
6936 this._frameSprite.children.forEach(child => {
6937 child.bitmap = this._windowskin;
6938 });
6939 this._setRectPartsGeometry(this._frameSprite, srect, drect, 24);
6940 } // _refreshFrame
6941
6942 _refreshCursor() {
6943 const drect = this._cursorRect.clone();
6944 const srect = { x: 96, y: 96, width: 48, height: 48 };
6945 this._cursorSprite.children.forEach(child => {
6946 child.bitmap = this._windowskin;
6947 });
6948 this._setRectPartsGeometry(this._cursorSprite, srect, drect, 4);
6949 } // _refreshCursor
6950
6951 _setRectPartsGeometry(sprite, srect, drect, m) {
6952 const [sx, sy, sw, sh] = [srect.x, srect.y, srect.width, srect.height];
6953 const [dx, dy, dw, dh] = [drect.x, drect.y, drect.width, drect.height];
6954 const [smw, smh] = [sw - m * 2, sh - m * 2];
6955 const [dmw, dmh] = [dw - m * 2, dh - m * 2];
6956 const children = sprite.children;
6957 sprite.setFrame(0, 0, dw, dh);
6958 sprite.move(dx, dy);
6959 /** @todo Extracts these codes into a well-named function */
6960 // corner
6961 children[0].setFrame(sx, sy, m, m);
6962 children[1].setFrame(sx + sw - m, sy, m, m);
6963 children[2].setFrame(sx, sy + sw - m, m, m);
6964 children[3].setFrame(sx + sw - m, sy + sw - m, m, m);
6965 children[0].move(0, 0);
6966 children[1].move(dw - m, 0);
6967 children[2].move(0, dh - m);
6968 children[3].move(dw - m, dh - m);
6969 //
6970 /** @todo Extracts these codes into a well-named function */
6971 // edge
6972 children[4].move(m, 0);
6973 children[5].move(m, dh - m);
6974 children[6].move(0, m);
6975 children[7].move(dw - m, m);
6976 children[4].setFrame(sx + m, sy, smw, m);
6977 children[5].setFrame(sx + m, sy + sw - m, smw, m);
6978 children[6].setFrame(sx, sy + m, m, smh);
6979 children[7].setFrame(sx + sw - m, sy + m, m, smh);
6980 const [scaleX, scaleY] = [dmw / smw, dmh / smh];
6981 children[4].scale.x = children[5].scale.x = scaleX;
6982 children[6].scale.y = children[7].scale.y = scaleY;
6983 //
6984 /** @todo Extracts these codes into a well-named function */
6985 // center
6986 if (children[8]) {
6987 children[8].setFrame(sx + m, sy + m, smw, smh);
6988 children[8].move(m, m);
6989 [children[8].scale.x, children[8].scale.y] = [scaleX, scaleY];
6990 }
6991 //
6992 children.forEach(child => child.visible = dw > 0 && dh > 0);
6993 } // _setRectPartsGeometry
6994
6995 _refreshArrows() {
6996 /** @todo Figures out the difference between 0 + p and p */
6997 const p = 24, [q, sx, sy] = [p / 2, 96 + p, 0 + p];
6998 //
6999 // Edited to help plugins alter the arrow refresh in better ways
7000 this._refreshDownArrow(p, q, sx, sy);
7001 this._refreshUpArrow(p, q, sx, sy);
7002 //
7003 } // _refreshArrows
7004
7005 _refreshPauseSign() {
7006 const [sx, sy, p] = [144, 96, 24];
7007 this._pauseSignSprite.bitmap = this._windowskin;
7008 this._pauseSignSprite.anchor.x = 0.5;
7009 this._pauseSignSprite.anchor.y = 1;
7010 this._pauseSignSprite.move(this._width / 2, this._height);
7011 this._pauseSignSprite.setFrame(sx, sy, p, p);
7012 this._pauseSignSprite.alpha = 0;
7013 } // _refreshPauseSign
7014
7015 _updateClientArea() {
7016 const pad = this._padding;
7017 this._clientArea.move(pad, pad);
7018 this._clientArea.x = pad - this.origin.x;
7019 this._clientArea.y = pad - this.origin.y;
7020 // Edited to help plugins alter client area visibility in better ways
7021 this._clientArea.visible = this._isUpdatedClientAreaVisible();
7022 //
7023 } // _updateClientArea
7024
7025 _updateFrame() { this._frameSprite.visible = this.frameVisible; }
7026
7027 _updateContentsBack() {
7028 const bitmap = this._contentsBackSprite.bitmap;
7029 if (!bitmap) return;
7030 this._contentsBackSprite.setFrame(0, 0, bitmap.width, bitmap.height);
7031 } // _updateContentsBack
7032
7033 _updateCursor() {
7034 this._cursorSprite.alpha = this._makeCursorAlpha();
7035 this._cursorSprite.visible = this.isOpen() && this.cursorVisible;
7036 this._cursorSprite.x = this._cursorRect.x;
7037 this._cursorSprite.y = this._cursorRect.y;
7038 } // _updateCursor
7039
7040 _makeCursorAlpha() {
7041 const blinkCount = this._animationCount % 40;
7042 const baseAlpha = this.contentsOpacity / 255;
7043 if (!this.active) return baseAlpha;
7044 if (blinkCount < 20) return baseAlpha - blinkCount / 32;
7045 return baseAlpha - (40 - blinkCount) / 32;
7046 } // _makeCursorAlpha
7047
7048 _updateContents() {
7049 const bitmap = this._contentsSprite.bitmap;
7050 if (!bitmap) return;
7051 this._contentsSprite.setFrame(0, 0, bitmap.width, bitmap.height);
7052 } // _updateContents
7053
7054 _updateArrows() {
7055 this._downArrowSprite.visible = this.isOpen() && this.downArrowVisible;
7056 this._upArrowSprite.visible = this.isOpen() && this.upArrowVisible;
7057 } // _updateArrows
7058
7059 _updatePauseSign() {
7060 const x = Math.floor(this._animationCount / 16) % 2;
7061 const y = Math.floor(this._animationCount / 16 / 2) % 2;
7062 const [sx, sy, p] = [144, 96, 24];
7063 // Edited to help plugins update the pause sign alpha in better ways
7064 this._updatePauseSignAlpha();
7065 //
7066 this._pauseSignSprite.setFrame(sx + x * p, sy + y * p, p, p);
7067 this._pauseSignSprite.visible = this.isOpen();
7068 } // _updatePauseSign
7069
7070 _updateFilterArea() {
7071 const pos = this._clientArea.worldTransform.apply(new Point(0, 0));
7072 const filterArea = this._clientArea.filterArea;
7073 filterArea.x = pos.x + this.origin.x;
7074 filterArea.y = pos.y + this.origin.y;
7075 filterArea.width = this.innerWidth;
7076 filterArea.height = this.innerHeight;
7077 } // _updateFilterArea
7078
7079 /**
7080 * Initializes all private variables of this window
7081 * Idempotent
7082 * @author DoubleX @since 0.9.5 @version 0.9.5
7083 */
7084 _initPrivateVars() {
7085 [this._isWindow, this._windowskin] = [true, null];
7086 this._width = this._height = 0;
7087 this._cursorRect = new Rectangle();
7088 [this._openness, this._animationCount] = [255, 0];
7089 [this._padding, this._margin] = [12, 4];
7090 [this._colorTone, this._innerChildren] = [[0, 0, 0, 0], []];
7091 this._container = this._backSprite = this._frameSprite = null;
7092 this._contentsBackSprite = this._cursorSprite = null;
7093 this._contentsSprite = this._downArrowSprite = null;
7094 this._upArrowSprite = this._pauseSignSprite = null;
7095 } // _initPrivateVars
7096
7097 /**
7098 * Initializes all public variables of this window
7099 * Idempotent
7100 * @author DoubleX @since 0.9.5 @version 0.9.5
7101 */
7102 _initPublicVars() {
7103 /**
7104 * The origin point of the window for scrolling.
7105 *
7106 * @type Point
7107 */
7108 this.origin = new Point();
7109 /**
7110 * The active state for the window.
7111 *
7112 * @type boolean
7113 */
7114 this.active = true;
7115 /**
7116 * The visibility of the frame.
7117 *
7118 * @type boolean
7119 */
7120 this.frameVisible = true;
7121 /**
7122 * The visibility of the cursor.
7123 *
7124 * @type boolean
7125 */
7126 this.cursorVisible = true;
7127 /**
7128 * The visibility of the down scroll arrow.
7129 *
7130 * @type boolean
7131 */
7132 this.downArrowVisible = false;
7133 /**
7134 * The visibility of the up scroll arrow.
7135 *
7136 * @type boolean
7137 */
7138 this.upArrowVisible = false;
7139 /**
7140 * The visibility of the pause sign.
7141 *
7142 * @type boolean
7143 */
7144 this.pause = false;
7145 } // _initPublicVars
7146
7147 /**
7148 * Sets the new cursor width and height followed by a cursor refresh
7149 * Idempotent
7150 * @author DoubleX @since 0.9.5 @version 0.9.5
7151 * @param {int} cw - The truncated width of the cursor
7152 * @param {int} ch - The truncated height of the cursor
7153 */
7154 _setNewCursorWH(cw, ch) {
7155 [this._cursorRect.width, this._cursorRect.height] = [cw, ch];
7156 this._refreshCursor();
7157 } // _setNewCursorWH
7158
7159 /**
7160 * Draws the window shape into PIXI.Graphics object used by WindowLayer
7161 * Idempotent
7162 * @author DoubleX @since 0.9.5 @version 0.9.5
7163 * @param {PIXI.Graphics} graphics - The Pixi graphics drawing the shape
7164 */
7165 _drawShapeWithGraphics(graphics) {
7166 const height = (this.height * this._openness) / 255;
7167 const y = this.y + (this.height - height) / 2;
7168 graphics.beginFill(0xffffff);
7169 graphics.drawRoundedRect(this.x, y, this.width, height, 0);
7170 graphics.endFill();
7171 } // _drawShapeWithGraphics
7172
7173 /**
7174 * Nullipotent
7175 * @author DoubleX @since 0.9.5 @version 0.9.5
7176 * @returns {Sprite} The new client area sprite of this window
7177 */
7178 _newClientArea() {
7179 const clientArea = new Sprite();
7180 clientArea.filters = [new PIXI.filters.AlphaFilter()];
7181 clientArea.filterArea = new Rectangle();
7182 clientArea.move(this._padding, this._padding);
7183 return clientArea;
7184 } // _newClientArea
7185
7186 /**
7187 * Refreshes the back sprite of this window
7188 * Idempotent
7189 * @author DoubleX @since 0.9.5 @version 0.9.5
7190 * @param {number} w - The width of this window excluding the margins
7191 * @param {number} h - The height of this window excluding the margins
7192 */
7193 _refreshBackSprite(w, h) {
7194 this._backSprite.bitmap = this._windowskin;
7195 /** @todo Figures out where does 96 come from */
7196 this._backSprite.setFrame(0, 0, 96, 96);
7197 this._backSprite.move(this._margin, this._margin);
7198 [this._backSprite.scale.x, this._backSprite.scale.y] = [w / 96, h / 96];
7199 //
7200 this._backSprite.setColorTone(this._colorTone);
7201 } // _refreshBackSprite
7202
7203 /**
7204 * Refreshes the back tiling sprite of this window
7205 * Idempotent
7206 * @author DoubleX @since 0.9.5 @version 0.9.5
7207 * @param {number} w - The width of this window excluding the margins
7208 * @param {number} h - The height of this window excluding the margins
7209 */
7210 _refreshBackTilingSprite(w, h) {
7211 const tilingSprite = this._backSprite.children[0];
7212 tilingSprite.bitmap = this._windowskin;
7213 /** @todo Figures out where does 96 come from */
7214 tilingSprite.setFrame(0, 96, 96, 96);
7215 tilingSprite.move(0, 0, w, h);
7216 [tilingSprite.scale.x, tilingSprite.scale.y] = [96 / w, 96 / h];
7217 //
7218 } // _refreshBackTilingSprite
7219
7220 /**
7221 * Refreshes the down arrow sprite of this window
7222 * Idempotent
7223 * @author DoubleX @since 0.9.5 @version 0.9.5
7224 * @param {number} p - The new width of the frame of the down arrow sprite
7225 * @param {number} q - The new height of the frame of the down arrow sprite
7226 * @param {number} sx - The source x position the down arrow sprite frame
7227 * @param {number} sy - The source y position the down arrow sprite frame
7228 */
7229 _refreshDownArrow(p, q, sx, sy) {
7230 this._downArrowSprite.bitmap = this._windowskin;
7231 this._downArrowSprite.anchor.x = this._downArrowSprite.anchor.y = 0.5;
7232 this._downArrowSprite.setFrame(sx + q, sy + q + p, p, q);
7233 this._downArrowSprite.move(this._width / 2, this._height - q);
7234 } // _refreshDownArrow
7235
7236 /**
7237 * Refreshes the up arrow sprite of this window
7238 * Idempotent
7239 * @author DoubleX @since 0.9.5 @version 0.9.5
7240 * @param {number} p - The new width of the frame of the up arrow sprite
7241 * @param {number} q - The new height of the frame of the up arrow sprite
7242 * @param {number} sx - The source x position the up arrow sprite frame
7243 * @param {number} sy - The source y position the up arrow sprite frame
7244 */
7245 _refreshUpArrow(p, q, sx, sy) {
7246 this._upArrowSprite.bitmap = this._windowskin;
7247 this._upArrowSprite.anchor.x = this._upArrowSprite.anchor.y = 0.5;
7248 this._upArrowSprite.setFrame(sx + q, sy, p, q);
7249 this._upArrowSprite.move(this._width / 2, q);
7250 } // _refreshUpArrow
7251
7252 /**
7253 * Nullipotent
7254 * @author DoubleX @since 0.9.5 @version 0.9.5
7255 * @returns {boolean} If the updated client area should be visible
7256 */
7257 _isUpdatedClientAreaVisible() {
7258 return this.innerWidth > 0 && this.innerHeight > 0 && this.isOpen();
7259 } // _isUpdatedClientAreaVisible
7260
7261 /**
7262 * Updates the alpha value of the pause sign sprite in this window
7263 * Idempotent
7264 * @author DoubleX @since 0.9.5 @version 0.9.5
7265 */
7266 _updatePauseSignAlpha() {
7267 const sprite = this._pauseSignSprite;
7268 if (!this.pause) return sprite.alpha = 0;
7269 if (sprite.alpha < 1) sprite.alpha = Math.min(sprite.alpha + 0.1, 1);
7270 } // _updatePauseSignAlpha
7271
7272} // Window
7273// It's just to play safe in case of any plugin extending PIXI.Container in ES6
7274ES6ExtendedClassAlias.inherit(Window);
7275//
7276
7277/*----------------------------------------------------------------------------
7278 * # Rewritten class: WindowLayer
7279 * - Rewrites it into the ES6 standard
7280 *----------------------------------------------------------------------------*/
7281
7282//-----------------------------------------------------------------------------
7283/**
7284 * The layer which contains game windows.
7285 *
7286 * @class
7287 * @extends PIXI.Container
7288 */
7289class WindowLayer extends PIXI.Container {
7290
7291 /**
7292 * Updates the window layer for each frame.
7293 */
7294 update() {
7295 this.children.forEach(child => { if (child.update) child.update(); });
7296 } // update
7297
7298 /**
7299 * Renders the object using the WebGL renderer.
7300 *
7301 * @param {PIXI.Renderer} renderer - The renderer.
7302 */
7303 render(renderer) {
7304 if (!this.visible) return;
7305 const [graphics, gl] = [new PIXI.Graphics(), renderer.gl];
7306 const children = this.children.clone();
7307 renderer.framebuffer.forceStencil();
7308 graphics.transform = this.transform;
7309 renderer.batch.flush();
7310 gl.enable(gl.STENCIL_TEST);
7311 while (!children.isEmpty()) {
7312 const win = children.pop();
7313 if (!win._isWindow || !win.visible || win.openness <= 0) continue;
7314 // Edited to help plugins alter render child behaviors in better way
7315 this._renderChild(renderer, graphics, gl, win);
7316 //
7317 }
7318 gl.disable(gl.STENCIL_TEST);
7319 gl.clear(gl.STENCIL_BUFFER_BIT);
7320 gl.clearStencil(0);
7321 renderer.batch.flush();
7322 this.children.forEach(child => {
7323 if (!child._isWindow && child.visible) child.render(renderer);
7324 });
7325 renderer.batch.flush();
7326 } // render
7327
7328 /**
7329 * Renders a child of this window layer
7330 * Idempotent
7331 * @author DoubleX @since 0.9.5 @version 0.9.5
7332 * @param {PIXI.Renderer} renderer - The renderer
7333 * @param {PIXI.Graphics} graphics - The Pixi graphics for drawing/rendering
7334 * @param {WebGLRenderingContext} gl - The pixi WebGL rendering context
7335 * @param {Window} win - The window as the child of this window layer
7336 */
7337 _renderChild(renderer, graphics, gl, win) {
7338 gl.stencilFunc(gl.EQUAL, 0, ~0);
7339 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
7340 win.render(renderer);
7341 renderer.batch.flush();
7342 graphics.clear();
7343 win.drawShape(graphics);
7344 gl.stencilFunc(gl.ALWAYS, 1, ~0);
7345 gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
7346 gl.blendFunc(gl.ZERO, gl.ONE);
7347 graphics.render(renderer);
7348 renderer.batch.flush();
7349 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
7350 } // _renderChild
7351
7352} // WindowLayer
7353// It's just to play safe in case of any plugin extending PIXI.Container in ES6
7354ES6ExtendedClassAlias.inherit(WindowLayer);
7355//
7356