diff --git a/Games/Contra-Shooter/Contra-Shooter.png b/Games/Contra-Shooter/Contra-Shooter.png new file mode 100644 index 0000000000..dde2203b9c Binary files /dev/null and b/Games/Contra-Shooter/Contra-Shooter.png differ diff --git a/Games/Contra-Shooter/Gruntfile.js b/Games/Contra-Shooter/Gruntfile.js new file mode 100644 index 0000000000..3c5b505722 --- /dev/null +++ b/Games/Contra-Shooter/Gruntfile.js @@ -0,0 +1,188 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + concat: { + dist: { + src: [ + 'lib/melonJS.js', + 'lib/plugins/*.js', + 'js/game.js', + 'build/js/resources.js', + 'js/**/*.js', + ], + dest: 'build/js/app.js' + } + }, + + copy: { + dist: { + files: [{ + src: 'index.css', + dest: 'build/index.css' + },{ + src: 'main.js', + dest: 'build/main.js' + },{ + src: 'manifest.json', + dest: 'build/manifest.json' + },{ + src: 'package.json', + dest: 'build/package.json' + },{ + src: 'data/**/*', + dest: 'build/', + expand: true + },{ + src: 'icons/*', + dest: 'build/', + expand: true + }] + } + }, + + clean: { + app: ['build/js/app.js'], + dist: ['build/','bin/'], + }, + + processhtml: { + dist: { + options: { + process: true, + data: { + title: '<%= pkg.name %>', + } + }, + files: { + 'build/index.html': ['index.html'] + } + } + }, + + replace : { + dist : { + options : { + usePrefix : false, + force : true, + patterns : [ + { + match : /this\._super\(\s*([\w\.]+)\s*,\s*["'](\w+)["']\s*(,\s*)?/g, + replacement : '$1.prototype.$2.apply(this$3' + }, + ], + }, + files : [ + { + src : [ 'build/js/app.js' ], + dest : 'build/js/app.js' + } + ] + }, + }, + + uglify: { + options: { + report: 'min', + preserveComments: 'some' + }, + dist: { + files: { + 'build/js/app.min.js': [ + 'build/js/app.js' + ] + } + } + }, + + connect: { + server: { + options: { + port: 3000, + keepalive: false + } + } + }, + + 'download-electron': { + version: '1.4.6', + outputDir: 'bin', + rebuild: false, + }, + + asar: { + dist: { + cwd: 'build', + src: ['**/*', '!js/app.js'], + expand: true, + dest: 'bin/' + ( + process.platform === 'darwin' + ? 'Electron.app/Contents/Resources/' + : 'resources/' + ) + 'app.asar' + }, + }, + + resources: { + dist: { + options: { + dest: 'build/js/resources.js', + varname: 'game.resources', + }, + files: [{ + src: ['data/bgm/**/*', 'data/sfx/**/*'], + type: 'audio' + },{ + src: ['data/img/**/*.png'], + type: 'image' + },{ + src: ['data/img/**/*.json'], + type: 'json' + },{ + src: ['data/map/**/*.tmx', 'data/map/**/*.json'], + type: 'tmx' + },{ + src: ['data/map/**/*.tsx'], + type: 'tsx' + }] + } + }, + + watch: { + resources: { + files: ['data/**/*'], + tasks: ['resources'], + options: { + spawn: false, + }, + }, + }, + + }); + + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-processhtml'); + grunt.loadNpmTasks("grunt-replace"); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-download-electron'); + grunt.loadNpmTasks('grunt-asar'); + + // Custom Tasks + grunt.loadTasks('tasks'); + + grunt.registerTask('default', [ + 'resources', + 'concat', + 'replace', + 'uglify', + 'copy', + 'processhtml', + 'clean:app', + ]); + grunt.registerTask('dist', ['default', 'download-electron', 'asar']); + grunt.registerTask('serve', ['resources', 'connect', 'watch']); +} diff --git a/Games/Contra-Shooter/README.md b/Games/Contra-Shooter/README.md new file mode 100644 index 0000000000..1f750ac09a --- /dev/null +++ b/Games/Contra-Shooter/README.md @@ -0,0 +1,45 @@ +# **Contra-Shooter** + +--- + +
+ +## **Description 📃** + +- Contra is an action-packed run-and-gun shooter game where players battle through various levels filled with enemies and obstacles. The game offers a mix of intense combat and strategic gameplay, challenging players to use their skills and reflexes to overcome each stage. + +## **Functionalities 🎮** + +- Single-player and co-op multiplayer modes. +- Various weapons and power-ups. +- Multiple levels with unique environments and enemies. +- Boss battles at the end of each level. +- Save and load game progress. +- Customizable controls and settings. + +
+ +## **How to play? 🕹ī¸** + +- Start the game and choose your mode (single-player or co-op multiplayer). +- Use the arrow keys or joystick to move your character. +- Press the shoot button to fire your weapon at enemies. +- Collect power-ups and weapons to enhance your abilities. +- Navigate through levels, avoid or destroy obstacles, and defeat enemies. +- Face the boss at the end of each level and defeat it to progress. +- Use the pause menu to access settings, save/load game, and more. + +
+ +## **Screenshots 📸** + +
+ + +![Contra-Shooter](https://github.com/VesperAkshay/GameZone/assets/118452811/f0d2261f-33ee-488d-8fb9-d320a334d708) + + +
+ +## **Working video 📹** + diff --git a/Games/Contra-Shooter/build/js/resources.js b/Games/Contra-Shooter/build/js/resources.js new file mode 100644 index 0000000000..3684b93d62 --- /dev/null +++ b/Games/Contra-Shooter/build/js/resources.js @@ -0,0 +1,107 @@ +game.resources = [ + { + "name": "dst-inertexponent", + "type": "audio", + "src": "data/bgm/" + }, + { + "name": "cling", + "type": "audio", + "src": "data/sfx/" + }, + { + "name": "jump", + "type": "audio", + "src": "data/sfx/" + }, + { + "name": "stomp", + "type": "audio", + "src": "data/sfx/" + }, + { + "name": "icon_badge_24_48", + "type": "image", + "src": "data/img/gui/icon_badge_24_48.png" + }, + { + "name": "icon_select_30_25", + "type": "image", + "src": "data/img/gui/icon_select_30_25.png" + }, + { + "name": "img_captured", + "type": "image", + "src": "data/img/gui/img_captured.png" + }, + { + "name": "img_neslogo", + "type": "image", + "src": "data/img/gui/img_neslogo.png" + }, + { + "name": "img_splashpage", + "type": "image", + "src": "data/img/gui/img_splashpage.png" + }, + { + "name": "contra-tile-map", + "type": "image", + "src": "data/img/map/contra-tile-map.png" + }, + { + "name": "bad-guys", + "type": "image", + "src": "data/img/sprite/bad-guys.png" + }, + { + "name": "bill-lance", + "type": "image", + "src": "data/img/sprite/bill-lance.png" + }, + { + "name": "explosion-sprite", + "type": "image", + "src": "data/img/sprite/explosion-sprite.png" + }, + { + "name": "power-ups-sprite", + "type": "image", + "src": "data/img/sprite/power-ups-sprite.png" + }, + { + "name": "rock_platform-sprite", + "type": "image", + "src": "data/img/sprite/rock_platform-sprite.png" + }, + { + "name": "bad-guys", + "type": "json", + "src": "data/img/sprite/bad-guys.json" + }, + { + "name": "bill-lance", + "type": "json", + "src": "data/img/sprite/bill-lance.json" + }, + { + "name": "contra-lvl-0", + "type": "tmx", + "src": "data/map/contra-lvl-0.tmx" + }, + { + "name": "contra-lvl-1", + "type": "tmx", + "src": "data/map/contra-lvl-1.tmx" + }, + { + "name": "contra-tileset-1", + "type": "tmx", + "src": "data/map/contra-tileset-1.tmx" + }, + { + "name": "contra-tileset-2", + "type": "tmx", + "src": "data/map/contra-tileset-2.tmx" + } +]; \ No newline at end of file diff --git a/Games/Contra-Shooter/data/bgm/.placeholder b/Games/Contra-Shooter/data/bgm/.placeholder new file mode 100644 index 0000000000..60fc579a73 --- /dev/null +++ b/Games/Contra-Shooter/data/bgm/.placeholder @@ -0,0 +1,2 @@ +docs will be placed here when built + diff --git a/Games/Contra-Shooter/data/bgm/dst-inertexponent.mp3 b/Games/Contra-Shooter/data/bgm/dst-inertexponent.mp3 new file mode 100644 index 0000000000..3a3361c0cd Binary files /dev/null and b/Games/Contra-Shooter/data/bgm/dst-inertexponent.mp3 differ diff --git a/Games/Contra-Shooter/data/bgm/dst-inertexponent.ogg b/Games/Contra-Shooter/data/bgm/dst-inertexponent.ogg new file mode 100644 index 0000000000..8310b52729 Binary files /dev/null and b/Games/Contra-Shooter/data/bgm/dst-inertexponent.ogg differ diff --git a/Games/Contra-Shooter/data/fnt/LICENSE.txt b/Games/Contra-Shooter/data/fnt/LICENSE.txt new file mode 100644 index 0000000000..141e49f4d0 --- /dev/null +++ b/Games/Contra-Shooter/data/fnt/LICENSE.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011, Cody "CodeMan38" Boisclair (cody@zone38.net), +with Reserved Font Name "Press Start". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/Games/Contra-Shooter/data/fnt/PressStart2P.fnt b/Games/Contra-Shooter/data/fnt/PressStart2P.fnt new file mode 100644 index 0000000000..e37ac831bd --- /dev/null +++ b/Games/Contra-Shooter/data/fnt/PressStart2P.fnt @@ -0,0 +1,100 @@ +info face=PressStart2P size=24 bold=0 italic=0 charset= unicode= stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=0,0 outline=0 +common lineHeight=24 base=24 scaleW=256 scaleH=512 pages=1 packed=0 +page id=0 file="PressStart2P.png" +chars count=95 +char id=32 x=1 y=1 width=0 height=0 xoffset=0 yoffset=24 xadvance=22 page=0 chnl=15 +char id=33 x=1 y=2 width=9 height=21 xoffset=6 yoffset=0 xadvance=22 page=0 chnl=15 +char id=34 x=1 y=24 width=15 height=9 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=35 x=11 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=36 x=1 y=34 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=37 x=1 y=56 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=38 x=1 y=78 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=39 x=17 y=23 width=6 height=9 xoffset=6 yoffset=0 xadvance=22 page=0 chnl=15 +char id=40 x=1 y=100 width=12 height=21 xoffset=6 yoffset=0 xadvance=22 page=0 chnl=15 +char id=41 x=1 y=122 width=12 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=42 x=1 y=144 width=21 height=15 xoffset=0 yoffset=3 xadvance=22 page=0 chnl=15 +char id=43 x=14 y=100 width=18 height=15 xoffset=3 yoffset=3 xadvance=22 page=0 chnl=15 +char id=44 x=14 y=116 width=9 height=9 xoffset=3 yoffset=15 xadvance=22 page=0 chnl=15 +char id=45 x=14 y=126 width=18 height=3 xoffset=3 yoffset=9 xadvance=22 page=0 chnl=15 +char id=46 x=24 y=116 width=6 height=6 xoffset=6 yoffset=15 xadvance=22 page=0 chnl=15 +char id=47 x=23 y=33 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=48 x=33 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=49 x=23 y=55 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=50 x=23 y=77 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=51 x=42 y=55 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=52 x=45 y=23 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=53 x=55 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=54 x=1 y=160 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=55 x=1 y=182 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=56 x=1 y=204 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=57 x=1 y=226 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=58 x=1 y=248 width=6 height=15 xoffset=6 yoffset=3 xadvance=22 page=0 chnl=15 +char id=59 x=1 y=264 width=9 height=18 xoffset=3 yoffset=3 xadvance=22 page=0 chnl=15 +char id=60 x=1 y=283 width=15 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=61 x=45 y=45 width=21 height=9 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=62 x=11 y=248 width=15 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=63 x=23 y=130 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=64 x=33 y=99 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=65 x=45 y=77 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=66 x=64 y=55 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=67 x=67 y=23 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=68 x=77 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=69 x=23 y=152 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=70 x=23 y=174 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=71 x=23 y=196 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=72 x=23 y=218 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=73 x=1 y=305 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=74 x=17 y=270 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=75 x=27 y=240 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=76 x=45 y=121 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=77 x=55 y=99 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=78 x=67 y=77 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=79 x=86 y=45 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=80 x=89 y=23 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=81 x=99 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=82 x=45 y=143 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=83 x=64 y=121 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=84 x=77 y=99 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=85 x=89 y=67 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=86 x=108 y=45 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=87 x=111 y=23 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=88 x=121 y=1 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=89 x=45 y=165 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=90 x=45 y=187 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=91 x=64 y=165 width=12 height=21 xoffset=6 yoffset=0 xadvance=22 page=0 chnl=15 +char id=92 x=67 y=143 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=93 x=86 y=121 width=12 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=94 x=27 y=262 width=15 height=6 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=95 x=89 y=89 width=21 height=3 xoffset=0 yoffset=21 xadvance=22 page=0 chnl=15 +char id=96 x=33 y=121 width=6 height=6 xoffset=9 yoffset=0 xadvance=22 page=0 chnl=15 +char id=97 x=96 y=93 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=98 x=111 y=67 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=99 x=130 y=45 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=100 x=133 y=23 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=101 x=143 y=1 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=102 x=45 y=209 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=103 x=1 y=327 width=21 height=18 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=104 x=20 y=292 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=105 x=39 y=269 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=106 x=49 y=231 width=15 height=24 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=107 x=64 y=209 width=21 height=21 xoffset=0 yoffset=0 xadvance=22 page=0 chnl=15 +char id=108 x=67 y=187 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=109 x=77 y=165 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=110 x=89 y=143 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=111 x=99 y=109 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=112 x=118 y=89 width=21 height=18 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=113 x=133 y=61 width=21 height=18 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=114 x=152 y=45 width=18 height=15 xoffset=3 yoffset=6 xadvance=22 page=0 chnl=15 +char id=115 x=99 y=125 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=116 x=155 y=17 width=18 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=117 x=165 y=1 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=118 x=1 y=346 width=18 height=15 xoffset=3 yoffset=6 xadvance=22 page=0 chnl=15 +char id=119 x=1 y=362 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=120 x=20 y=346 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=121 x=23 y=314 width=21 height=18 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=122 x=42 y=291 width=21 height=15 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=123 x=58 y=256 width=12 height=21 xoffset=6 yoffset=0 xadvance=22 page=0 chnl=15 +char id=124 x=65 y=231 width=6 height=21 xoffset=9 yoffset=0 xadvance=22 page=0 chnl=15 +char id=125 x=1 y=378 width=12 height=21 xoffset=3 yoffset=0 xadvance=22 page=0 chnl=15 +char id=126 x=58 y=278 width=21 height=9 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 +char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=6 xadvance=22 page=0 chnl=15 \ No newline at end of file diff --git a/Games/Contra-Shooter/data/fnt/PressStart2P.ltr b/Games/Contra-Shooter/data/fnt/PressStart2P.ltr new file mode 100644 index 0000000000..78c4f30213 --- /dev/null +++ b/Games/Contra-Shooter/data/fnt/PressStart2P.ltr @@ -0,0 +1 @@ +{"settings":{"scalings":"1;","filename":"PressStart2P","postfixes":""},"glyphs":{"canvasWidth":"Auto","roundValues":true,"glyphs":" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~","powerOfTwo":true,"packMethod":1,"padding":1,"canvasHeight":"Auto","descriptionFormat":1},"font":{"spacing":-2,"data":"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OwEdATsBBTUrAR0BKQE9ASsBgAEAAQBAQEBAgIBAQP7A/sBAQEBAQEBAQAIAwMBAQICA/wCAgAEAAQCAgAPAQEBAgIBAQICAQEBAQICAQECAgICAQEBAQMBAgIBAQAAAAAIAAACAA4AEAAAnAC8AABM1KQEdATsBGQErAR0BKwEdASkBPQE7AT0BOwE9ASkBPQErAT0BOwEFNSsBHQE7AYABQAFAQEBAQEBA/wD/AMDAQED/AP8AQEBAQAIAwMDAwAPAQEBA/wD/AEBAQEBAQEBAQEBAQICAgICAgAACAQABAAIAA4AABwAPAAABNTsBHQErARE1OwEdASsBAQCAgICAgICAgAGAgICAAgCAgIAAAgCAAIACAAOAAA8AFwAAATU7AR0BKwEdASsBPQE7ARE1OwEdASsBAQCAgEBAgIBAQICAgIABgICAgEBAQEACAICAgAABAIAAgAMABAAANwAAATU7AR0BKwEdASsBHQErAR0BOwEdATsBHQE7AR0BKwE9ASsBPQErAT0BKwE9ATsBPQE7AT0BOwECAICAQEBAQEBAQEBAQEBAgIBAQEBAQEBAQEBAQEADwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAACAAABgAOAAwAABwAPAAARNSkBHQEpARE1KQEdASkBAcABwP5A/kABwAHA/kD+QAHAQEBAAUBAQEAAAAABAIAAgAMABAAANwAAEzU7AR0BOwEdATsBHQE7AR0BKwEdASsBHQErAR0BKwE9ATsBPQE7AT0BOwE9ASsBPQErAT0BKwGAgIBAQEBAQEBAQEBAQECAgEBAQEBAQEBAQEBAQAPAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAIAAACAA4AEAAAHAC8AACU1OwEdASsBAzUpAR0BOwEdASsBHQErAR0BKwE9ATsBPQE7AT0BKwEdASsBPQE7AQEAwMDAwIABQAFAQEBAQEBAwMCAgEBAwMCAgEBAwEBAQANAQEBAgIBAQEBAQEBAQEBAQECAgAAAAAIAAACAA4AEAAAnAC8AABM1KQEdATsBGQEpAT0BOwEdATsBPQEpARkBKQEdASkBPQErARkBOwEBNSsBHQE7AYABQAFAQED+wP7AwMBAQP7A/sABQAFA/sD+wEBAQEABgEBAQEADwEBAQP8A/wDAwICAwMD+wP7A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KwE9ATsBAYCAgEBA/wD/AEBAQEBAQAPAQMDAQEBAQEBAQEAAAAIAgAIAAwAEAAAXAB8AAAE1OwEdATsBHQErAR0BKwE9ASsBPQE7AQU1KwEdATsBAQDAwEBAQEDAwEBAQEABAEBAQEADwEBAQICAQEBAQICAgICAgAAAAAACAAABAAOAA4AANwBPAAARNTsBHQE7AT0BOwEdATsBHQE7AR0BKwEdASsBHQErAT0BKwEdASsBPQE7AT0BOwE9ASsBPQErAQU1KwEdATsBHQErAR0BOwE9ATsBPQErAYCAQECAgEBAQEBAQEBAgIBAQICAQEBAQEBAQEACAEBAQEBAQEBAQEBAQANAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAACAAAAgAOABAAAEwBbAAABNTsBGQErAT0BKwE9ATsBPQE7AQE1OwEZATsBPQE7AT0BOwE9ATsBPQE7AR0BKwEdASsBHQErAR0BKwEdASsBHQErAR0BKwE9ATsBPQE7AT0BKwE9ASsBPQE7AQMAQEBAQICAQEBAQP2AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAkBA/wD/AEBAQEBAQAHAQP8A/wBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgIBAQAAAAAEAAACAA4AEAABjAAATNTsBGQE7AT0BOwE9ATsBPQE7AT0BOwEdASsBHQErAR0BOwEdASsBHQE7AR0BKwE9ATsBPQE7AT0BKwE9ASsBHQErAR0BKwEdASsBHQErAT0BOwE9ATsBPQErAT0BKwE9ATsBgEBAQEBAQEBAQEBAQEBAQECAgEBAQEDAwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAA8BA/wD/AEBAQEBAQEBAQEBAQEBAgIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQEAAAAADAAAAgAOABAAAEwBjAGsAAAE1OwEZASsBPQErAT0BOwE9ATsBATU7AR0BKwEdATsBHQE7AT0BOwE9ATsBPQE7AR0BKwEdASsBHQErAR0BKwEdASsBHQErAR0BKwE9ATsBPQE7AT0BKwE9ATsBPQErAT0BKwEBNSsBHQE7AQMAQEBAQICAQEBAQP0AwMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA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AQDAwEBAQEBAQEBAgICAgEBAQEDAwICAQEBAQAEAQEBAQMDAQEADwEBAQEBAwMBAQEBAQEBAQEBAgIBAQMDAQEBAQEBAQEBAQAAAAAACAAAAAAOABAAAMwA7AAATNSkBHQE7ARkBKwEdASsBHQE7AR0BKwE9ASsBPQE7AT0BKwE9ASsBPQE7AT0BKQE9ASkBATUrAR0BOwGAAUABQEBAwMBAQICAgIBAQEBAgIBAQEBAAQABAP8A/wACAMDAwMADwEBAQP8A/wBAQEBAQEBAQEBAQEBAQEBAQEBAQP7AQEBAAAAAAAEAAACAA4AEAABHAAABNTsBHQErAR0BOwEdATsBHQErAT0BKwEdASsBHQE7AR0BOwE9ATsBHQErAR0BKQE9ASsBPQErAT0BOwE9ATsBPQE7AT0BOwECAEBAQECAgEBAgICAgEBAQECAgICAQED/AP8AQEBAQEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAQAAAIADgAQAACcAAAE1OwEdASsBHQE7AR0BKQEdASkBHQEpAT0BKwE9ATsBPQE7AT0BOwECAEBAQEDAwP7A/sABQAFA/oD+gEBAQECAgEBAA8BAQEBAQEBAwMBAQEBAwMBAQEBAAAACAAAAgAOABAAAQwBLAAABNTsBHQE7AR0BOwEdASsBPQErAR0BKwEdATsBHQE7AT0BOwEdASsBHQEpAT0BKwE9ASsBPQE7AT0BOwE9ASsBPQE7AQU1KwEdATsBAQDAwEBAQECAgICAQEBAQICAgIBAQP8A/wBAQEBAQEBAQEBAQEABAEBAQEADwEBAQICAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAIAAACAA4AEAAAjACsAAAE1OwEdATsBHQE7AR0BKQEdASkBHQEpAT0BKwE9ATsBPQE7AQU1KwEdATsBAQDAwEBAQED+wP7AAUABQP6A/oBAQEBAQEABAEBAQEADwEBAQEBAQEDAwEBAQEDAwICAQEBAQAAAAgAAAIADgAQAADcAPwAAATUpAR0BOwEdASsBPQErAR0BKwEdATsBHQE7AT0BOwEdASsBHQEpAT0BKwE9ASsBPQE7AT0BOwETNTsBHQErAQEAAQABAEBAgICAgEBAQECAgICAQED/AP8AQEBAQEBAQECAgICAgALAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAUBAQEAAAAAAAgAAAIADgAQAABcAHwAAEzUpAR0BKQEdASkBHQEpAT0BKwE9ATsBATU7AR0BKwGAAYABgP7A/sABQAFA/oD+gEBAQEABAICAgIACwEBAQMDAQEBAQMDAAUBAQEAAAAAAAQAAAIADgAQAAEcAABM1OwEdATsBPQE7AR0BKwEdATsBHQErAT0BKwEdASsBHQE7AR0BOwE9ATsBHQErAR0BKQE9ASsBPQErAT0BOwE9ATsBPQErAYCAgICAgIBAQEBAgICAgEBAQECAgICAQED/AP8AQEBAQEBAQEBAQAPAQEBAQEBAQICAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgIAAAAABAAAAgAOABAAALwAAEzU7AR0BOwE9ATsBHQErAR0BOwEdASkBHQEpAR0BKQE9ASsBPQE7AT0BOwE9ASsBgICAQECAgEBAgID+wP7AAUABQP6A/oBAQEBAQEBAQAPAQEBAQEBAQEBAQEDAwEBAQEDAwEBAQEAAAAACAAAAgAOABAAAKwA7AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BOwEdASsBHQErAR0BKQEZATsBPQErAQE1KwEdATsBPQE7AT0BKwGAgIBAQICAQEBAQEBAQEBAQP7A/sCAgEBAAYCAgICAQEBAQAPAQEBAQEBAQICAQEBAQEBAQEABQAFAQED+wEDAwEBAQEAAAAAAAwAAAIAEAAQAAAsAHwAnAAABNTsBHQErAR0BKwEBNTsBGQEpAT0BKwE9ATsBPQE7ARE1KwEdATsBAwCAgEBAQED+gICA/wD/AEBAQECAgEBAQEADQMCAgEBAAQCA/kD+QEBAwMBAQP7AwMDAAAAAAAIAAACAA4AEAAAfADcAABM1KQEdATsBHQE7AR0BKwEdASsBHQEpAT0BKwE9ATsBJTUrAR0BOwEdASsBHQE7AT0BOwE9ASsBgAEAAQBAQEBAQEBAQP8A/wBAQEBAAYBAQEBAQEBAQEBAQEADQMBAQEBAwMBAQEBAwMBAQMBAgIBAQICAQEDAwAACAAAAgAOABAAAHwAnAAABNTsBHQE7AR0BKwEZASkBPQErAT0BOwE9ATsBPQE7ARE1KwEdATsBAgCAgEBAQED+wP7AQEBAQICAQECAgICAA8BAQEBAQP7A/sBAQMDAQEBAQP5AwMDAAAIAAACAA4AEAAAXAB8AABkBKQEdASkBHQEpAR0BKQEdASkBHQEpARM1KQEdASkBAcABwP7A/sABAAEA/wD/AAFAAUD+QP5AgAFAAUD+wP7AAcABQEBAQEBAQEBAQEADQEBAQAADAAAAgAOABAAAGwAjACsAABM1KQEdATsBHQEpAR0BKQEdASkBPQErAT0BOwEFNSsBHQE7AQE1KQEdASkBgAFAAUBAQP7A/sABAAEA/sD+wEBAQEACAMDAwMD+AAFAAUD+wP7AAsBAQECAgEBAQEBAQMDAQEBAQAHAQEBAAAAAAAEAAACAA4AEAAAvAAATNTsBHQE7AT0BOwEdASsBHQE7AR0BKQEdASkBHQEpAR0BKQEdASkBGQE7AT0BKwGAQEDAwEBAQECAgP7A/sABAAEA/wD/AAFAAUD+QP5AgIBAQAPAQEBAQEBAQEBAQEBAQEBAQEBAQAFAAUBAQAACAAAAgAOABAAAMwA7AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BOwEdASkBHQEpAR0BKQE9ASsBPQE7AT0BOwE9ASsBATUrAR0BOwGAQEDAwEBAQEBAQEBA/sD+wAEAAQD+wP7AQEBAQEBAQEACAMDAwMADwEBAQEBAQEBAQEBAgIBAQEBAQEDAwEBAQED+wEBAQAAAAgAAAIADgAQAABcAHwAAGQEpAR0BKQEdASkBHQEpAR0BKQEdASkBATU7AR0BKwEBwAHA/sD+wAEAAQD/AP8AAUABQP5A/kABgICAgIABwAFAQEBAQEBAQEBAQANAQEBAAAAAAAMAAACAA4AEAAAbACMAKwAAEzUpAR0BOwEdASkBHQEpAR0BKQE9ASsBPQE7AQU1KwEdATsBATU7AR0BKwGAAUABQEBA/sD+wAEAAQD+wP7AQEBAQAIAwMDAwP8AgICAgALAQEBAgIBAQEBAQEDAwEBAQEABwEBAQAAAAAABAAAAAAOABAAALwAAGQEpAR0BKQEdASkBHQEpAR0BKQEdASsBHQErAR0BOwEdASsBPQErAT0BOwE9ASsBAcABwP7A/sABAAEA/wD/AAFAAUDAwEBAgICAgEBAQEDAwALAAUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAgAAAAADgAQAADMAOwAAEzUpAR0BOwEdASkBHQEpAR0BKwEdASsBHQE7AR0BKwE9ASsBPQE7AT0BKwE9ASsBPQE7AQU1KwEdATsBgAFAAUBAQP7A/sABAAEAgIBAQICAgIBAQEBAgIBAQEBAAgDAwMDAA8BAQECAgEBAQEBAQEBAQEBAQEBAQEBAQMDAQEBAQAAAAAEAAACAA4AEAAAvAAATNTsBHQE7AT0BOwEdASsBHQE7AR0BKQEdASkBHQEpAR0BKQEdASkBGQE7AT0BKwGAgIBAQICAQECAgP7A/sABAAEA/wD/AAFAAUD+QP5AgIBAQAPAQEBAQEBAQEBAQEBAQEBAQEBAQAFAAUBAQAACAAAAgAOABAAAMwA7AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BOwEdASkBHQEpAR0BKQE9ASsBPQE7AT0BOwE9ASsBATUrAR0BOwGAgIBAQICAQEBAQEBA/sD+wAEAAQD+wP7AQEBAQEBAQEACAMDAwMADwEBAQEBAQEBAQEBAgIBAQEBAQEDAwEBAQED+wEBAQAAAAgAAAIADgAQAACsAMwAAATU7AR0BOwEdATsBHQEpAR0BOwE9ASsBPQE7AR0BKQE9ASsBPQE7AT0BOwEFNSsBHQE7AQEAwMBAQEBA/sD+wMDAQEDAwP6A/oBAQEBAQEABAEBAQEADwEBAQEBAQEDAwEBAQEDAwEBAwMCAgEBAQEAAAAAAAwAAAAADgAQAACcALwA3AAABNTsBHQE7AR0BOwEZASsBHQEpAT0BKQE9ASkBPQErAT0BOwE9ATsBBTUrAR0BOwETNSsBHQE7AQEAwMBAQEBAQED+wP7AAQABAP8A/wBAQEBAQEABAEBAQECAwMDAwAPAQEBAQED+wP7AQEBAQEBAQECAgICAQEBAQP8AgICAAAAAAAEAAACAA4AEAAA3AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BKQEdATsBPQErAT0BOwEdASkBPQErAT0BOwE9ATsBPQErAYBAQMDAQEBAQICA/sD+wMDAQEDAwP6A/oBAQEBAQEBAQAPAQEBAQEBAQEBAQEDAwEBAQEDAwEBAwMBAQEBAAAIAAAAAA4AEAAAzADsAABM1OwEdATsBPQE7AR0BKwEdATsBGQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQE7AT0BKwEBNSsBHQE7AYBAQMDAQEBAQICAQED+wP7AAQABAP8A/wBAQEBAQEBAQAIAwMDAwAPAQEBAQEBAQEBA/sD+wEBAQEBAQEBAgIBAQEBA/oCAgIAAAAAAAgAAAIADgAQAAB8AJwAAEzUpAR0BKQEdATsBPQErAT0BOwEdASkBPQErAT0BOwEBNTsBHQErAYABgAGA/sD+wMDAQEDAwP6A/oBAQEBAAQCAgICAAsBAQEDAwEBAQEDAwEBAwMABQEBAQAAAAwAAAAADgAQAABsAIwArAAATNSkBGQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBBTUrAR0BOwEBNTsBHQErAYABgAGAQED+wP7AAQABAP8A/wBAQEBAAgDAwMDA/wCAgICAAsBA/sD+wEBAQEBAQEBAgICAgICAAkBAQEAAAAEAAAAAA4AEAAA/AAABNSkBHQEpAR0BKwEdATsBHQE7AT0BKwE9ATsBHQErAR0BKwEdASsBPQE7AT0BKwE9ASsBPQErAT0BOwE9ATsBAQABQAFA/wD/AEBAQECAgEBAwMCAgEBAgIBAQEBAQEBAQEBAQEADwEBAQEBAgIBAQEBAQEDAwEBAQEBAQEBAQEBAQICAQEAAAAAAAgAAAAADgAQAACsAMwAAATU7AR0BKwEdATsBGQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQE7AT0BOwEBNSsBHQE7AQGAgIBAQMDAQED+wP7AAQABAP8A/wBAQEBAQEBAQAEAwMDAwAPAQEBAQED+wP7AQEBAQEBAQECAgEBAQED+gICAgAAAAAIAAACAA4AEAAAXACcAAAE1OwEdATsBGQErAT0BKwEdASsBGQE7AQU1KwEdASsBHQE7AT0BKwEBAMDAgICAgMDAgICAgAEAQEBAQMDAQEADwEBAQP6A/oCAgICAAYABgEBAQECAgICAAAAAAAEAAACAA4AEAAArAAABNTsBHQE7AR0BKwE9ASsBHQErAR0BKQEdATsBHQErAT0BKwEdASsBGQE7AQEAwMBAQICAQEBAQAEAAQBAQICAwMCAgICAA8BAQEBAQEBAQEBAQEBAwMDAwMDAAYABgAAAAAACAAAAgAOABAAAFwAfAAAZATsBHQE7AT0BOwEZASsBPQErAR0BKwEBNSsBHQE7AYCAwMCAgICAwMCAgAKAwMDAwAJAAcBAQEBA/kD+QMDAwMACQEBAQAAAAQAAAIADgAQAACMAABM1OwEdATsBHQE7AR0BOwEZASsBGQErARkBKwEZASsBPQE7AYCAgEBAgIBAQICAgICAgEBAQEADwEBAQEBAQED/AP8AAQABAP8A/wABQAFAQEAAAgCAAIADgAQAACsAMwAAATU7AR0BOwE9ATsBHQErAR0BOwEdASsBHQE7AR0BKQE9ATsBPQErAT0BOwEFNSsBHQE7AQEAgICAgEBAQEBAQICAgID+gP6AgICAgEBAAQCAgICAA8BAQEBAQEBAQEBAQMDAQEBAQMDAgIBAQEBAAAACAIAAgAOABAAAEwAzAAABNTsBHQE7AR0BKQE9ATsBPQErARE1OwEdATsBPQE7AR0BKwEdASsBPQErAR0BKwE9ATsBAQDAwICA/oD+gICAQECAgICAQEBAQICAgIBAQEBAAkBAwMBAQEBAgIABwEBAQEBAQEBAQEBAQEBAQAAAAAIAgACAA4AEAAAXAB8AABM1KQEdASsBHQE7AR0BKQE9ATsBPQErARE1KQEdASkBgAGAAYCAgICA/oD+gICAgIABgAGA/oD+gALAQEBAwMBAQEBAwMABQEBAQAAAAgCAAIADgAQAABMAGwAAATU7ARkBOwEdASkBPQE7AT0BKwEDNSkBHQEpAQEAwMCAgP6A/oCAgEBAgAFAAUD+wP7AAsBA/wD/AEBAQEDAwAFAQEBAAAAAAAEAgACAA4AEAAAvAAABNTsBHQE7AT0BOwEdASsBHQE7AR0BKwEdATsBHQEpAT0BOwE9ASsBPQE7AT0BKwEBAEBAgIBAQEBAgICAgICA/oD+gICAgICAgEBAA8BAQEBAQEBAQEBAQMDAQEBAQMDAQEBAQAAAAQCAAIADgAQAACMAABM1OwEdATsBPQE7AR0BKwEZATsBHQEpAT0BOwE9ASsBPQErAYBAQMDAQEBAQICA/oD+gICAQEBAQAPAQEBAQEBAQP7A/sBAQEBAwMCAgAABAIAAAAOABAAALwAAEzUpAR0BKwEdATsBHQErAR0BKwEdATsBHQErAT0BKwE9ATsBPQErAT0BOwE9ASsBgAGAAYCAgICAwMBAQICAgIBAQEBAgICAgICAA8BAQEDAwEBAQEBAQEBAQEBAQEBAQEDAwAAAAAIAgAAAA4AEAAArADMAAAE1OwEdATsBHQErAR0BKwEdATsBHQErAT0BKwE9ATsBPQErAT0BOwE9ASsBEzU7AR0BKwEBAMDAgIDAwEBAgICAgEBAQECAgICAQECAgICAgALAQICAQEBAQEBAQEBAQEBAQEBAQEBAAUBAQEAAAAAAAgCAAIADgAQAABcAHwAAEzUpAR0BKwEdATsBHQEpAT0BOwE9ASsBATU7AR0BKwGAAYABgICAgID+gP6AgICAgAEAgICAgALAQEBAwMBAQEBAwMABQEBAQAAAAAABAIAAgAOAAwAAEwAAATU7ARkBOwEdASkBPQE7AT0BKwEBAMDAgID+gP6AgIBAQALAQP8A/wBAQEBAwMAAAAAAAgAAAIADgAQAAA8AJwAAARE7ARkBKwEdASkBPQEpAQE1KQEdASsBHQE7AR0BKQE9ATsBPQErAQKAgIBAQP7A/sABAAEA/YABAAEAQEBAQP8A/wBAQEBAAoABgP6A/oBAQEBAAsBAQEDAwEBAQEDAwAAAAAAEAAAAAAOABAAAEwAfACcALwAAATU7ARkBKwEdASkBPQE7ARkBKwElNTsBGQErAT0BKwEBNTsBHQErASU1OwEdASsBAgDAwEBA/wD/AMDAQED+AMDAgIBAQAKAgICAgP4AgICAgALAQP7A/sBAQEBAAQABAEBA/wD/AMDAAUBAQEBAQEBAAAAAAQAAAIADgAQAACcAAAE1OwEdATsBGQErAR0BKQE9ASsBPQE7AR0BOwEZASsBHQErAT0BOwEBgMDAQEBAQP7A/sBAQICAwMBAQICAQEADwEBAQP7A/sBAQEBAQEBAQAFAAUBAQEBAAAACAIAAAAOABAAAEwArAAABNTsBGQErAR0BKQE9ATsBPQErARE1OwEdATsBHQErAT0BKwEdASsBPQE7AQGAwMBAQP8A/wDAwEBAwMBAQICAQECAgEBAAkBA/wD/AEBAQEDAwAHAQEBAQEBAQEBAQEAAAgAAAAADgAQAADcAQwAAGQE7AR0BOwE9ATsBPQE7AR0BKwEdASsBHQE7AR0BOwEdATsBHQErAR0BKwEdASsBPQE7AT0BKwElNSsBHQE7AT0BKwGAgEBAQECAgEBAQEBAQEBAQEDAwEBAgIBAQICAAYBAQICAQEACgAGAgIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAQICAQEAAAgAAAAADgAQAAC8AOwAAGQE7AR0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwEdASsBHQErAT0BOwE9ASsBJTUrAR0BOwE9ASsBgICAgMDAQEBAQEBAQEDAwEBAgIBAQICAAYBAQICAQEACgAGAgIBAQEBAQEBAQEBAQEBAQEBAQEBAQMBAgIBAQAABAAAAgAOAAwAALwAAGQE7AR0BOwE9ATsBPQE7AR0BKwEdASsBHQE7AR0BOwEdASsBPQErAT0BKwEdASsBgICAgEBAgIBAQEBAQEBAQICAQECAgICAAcABQICAQEBAQEBAQEBAQEBAQEBAQEBAgIAAAQCAAIADgAQAABcAAAE1OwEdASsBHQErARkBKQEdASkBGQE7AQIAQEBAQEBAAQABAP6A/oDAwAPAQEBAQED/AP8AQEABgAGAAAAAAAEAAACAAwAEAAAjAAABNTsBHQErAR0BOwEZATsBHQEpAT0BOwE9ASsBPQE7AT0BOwEBgEBAQEBAQICA/oD+gICAQEBAQEBAA8BAQEBAQP8A/wBAQEBAwMBAQEBAAAAAAAEAgAAAA4AEAAAbAAATETsBGQEpAR0BKwEdASsBHQErAT0BOwE9ASsBgICAAQABAICAQECAgEBAgIACgAGA/sD+wEBAQEBAQEBAQEAAAAAAAQCAAAADgAQAACMAAAE1OwEZATsBHQErAR0BKwEdASsBPQE7AT0BKwE9ATsBGQErAQEAwMCAgICAQECAgEBAgICAgEBAA8BA/sD+wEBAQEBAQEBAQEBAQAEAAQAAAAAAAgCAAIADgAQAAAsAFwAAATU7AR0BKwEdASsBBRE7ARkBKQEdASkBAoCAgEBAQED+AICAAQABAP6A/oADQMCAgEBAQAHA/oD+gEBAAAIAAACAA4AEAAALAB8AAAE1OwEdASsBHQErAQE1OwEZATsBHQEpAT0BOwEZASsBAoCAgEBAQED+AMDAgID+gP6AgIBAQANAwICAQEABQED+gP6AQEBAQAFAAUAAAgCAAIADgAQAAAcAEwAAATU7AR0BKwElETsBGQEpAR0BKQECAICAgID+gICAAQABAP6A/oACgICAgEABwP6A/oBAQAACAAAAgAOABAAABwAbAAABNTsBHQErAQE1OwEZATsBHQEpAT0BOwEZASsBAoCAgICA/gDAwICA/oD+gICAQEACgICAgAHAQP6A/oBAQEBAAUABQAABAAAAgAOABAAAGwAAEzU7AR0BOwEdASsBHQEpAR0BKQE9ASsBPQE7AYCAgEBAQEABAAEA/oD+gEBAQEADQMCAgEBAwMBAQMDAQEAAAQCAAIADgAQAACMAAAE1OwEdATsBHQErAR0BOwEdASkBPQE7AT0BKwE9ATsBPQErAQEAwMBAQEBAgID+gP6AgIBAQEBAQEADwECAgEBAwMBAQEBAgIBAQICAAAABAAAAgAOABAAALwAAATU7AR0BKwEdASsBHQE7AR0BOwE9ATsBGQErAT0BKwE9ASsBHQErARkBOwE9ATsBAgBAQEBAQEBAQEBAgIDAwEBAQECAgMDAQEADwEBAQEBAQEBAQICA/sD+wEBAQECAgAFAAUBAQAAAAAABAAAAgAOABAAAIwAAATU7AR0BKwEdATsBHQE7ARkBKwEZASsBGQErARkBOwE9ATsBAgBAQEBAgIBAQICAwMCAgMDAQEADwEBAQEBAQED/AP8AAQABAP8A/wABQAFAQEAAAAAAAQAAAAADgAQAADMAABkBOwEdATsBHQE7AT0BOwEZASsBHQErAR0BKwE9ATsBPQE7AT0BKwE9ASsBPQErAR0BKwHAwEBAQECAgICAQECAgEBAgIBAQEBAQECAgAKAAYBAQEBAgID+gP6AQEBAQEBAQEBAQEBAQEDAwAAAAAEAAAAAA4ADgAAjAAAZASkBHQE7ARkBKwEdASsBHQErAT0BOwE9ATsBGQErARkBKwEBgAGAQECAgEBAgIBAQICAwMCAgAJAAUBAQP8A/wBAQEBAQEBAQAEAAQD/AP8AAAIAAACAA4AEAAArADcAABM1OwEdATsBPQE7AR0BKwEdATsBGQErAT0BKwE9ASsBHQErARkBOwE9ASsBATUrAR0BOwEdATsBgICAQECAgEBAgIDAwEBAQECAgICAQEACAICAQEBAQAPAQEBAQEBAQEBA/sD+wEBAQECAgAFAAUBAQP8AgEBAQEAAAAAAAQAAAIADgAQAACsAABM1OwEdATsBPQE7AR0BKwEdATsBHQE7ARkBKwEZASsBGQErARkBOwE9ASsBgICAQECAgEBAQEBAQICAwMCAgICAQEADwEBAQEBAQEBAQEBA/wD/AAEAAQD/AP8AAUABQEBAAAEAAACAA4AEAAAfAAARNTsBHQEpAR0BOwEZASsBGQErARkBKwEZASsBHQErAYCAAQABAEBAgIBAQICAQEBAQANAwICAQED/AP8AAQABAP8A/wABQAFAQEAAAAAAAQAAAAADgAQAACsAABkBOwEdATsBHQE7AT0BOwEZASsBHQEpAT0BOwE9ASsBPQErAT0BKwEdASsBwMBAQEBAgIBAQP8A/wDAwEBAQEBAQICAAoABgEBAQECAgP5A/kBAQEBAgIBAQEBAwMAAAQAAAAADgAOAABsAABkBKQEdATsBGQErAR0BKQE9ATsBGQErARkBKwEBgAGAQEBAQP8A/wDAwMDAgIACQAFAQED+wP7AQEBAQAFAAUD/AP8AAAAAAwAAAIADgAQAABcAHwAnAAATNSkBHQE7AR0BKwEdASkBPQErAT0BOwEFNSsBHQE7AQE1KQEdASkBgAFAAUBAQEBA/sD+wEBAQEACAMDAwMD+AAFAAUD+wP7AAsBAQEDAwEBAQEDAwMDAwMACwEBAQAAAAAADAAAAgAOABAAAFwAfACcAABM1KQEdATsBHQErAR0BKQE9ASsBPQE7AQU1KwEdATsBATUpAR0BKQGAAUABQEBAQED+wP7AQEBAQAIAwMDAwP4AAUABQP7A/sACwEBAQMDAQEBAQMDAwMDAwALAQEBAAAAAAAIAAACAA4AEAAAvADcAABM1OwEdATsBPQE7AR0BKwEdATsBHQE7AR0BKwEdASkBPQErAT0BOwE9ATsBPQErAQE1KwEdATsBgEBAwMBAQEBAQEBAQEBA/sD+wEBAQEBAQEBAAgDAwMDAA8BAQEBAQEBAQEBAQMDAQEBAQMDAQEBAQP5AwMDAAAACAAAAgAOABAAALwA3AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BOwEdASsBHQEpAT0BKwE9ATsBPQE7AT0BKwEBNSsBHQE7AYBAQMDAQEBAQEBAQEBAQP7A/sBAQEBAQEBAQAIAwMDAwAPAQEBAQEBAQEBAQEDAwEBAQEDAwEBAQED+QMDAwAAAAgAAAIADgAQAADMAOwAAATU7AR0BKwEdATsBPQE7AT0BOwEdASsBHQE7AR0BOwEdASsBHQEpAT0BKwE9ATsBPQE7AQE1KwEdATsBAQBAQEBAgIBAQEBAQEBAQEBAQED+wP7AQEBAQEBAAYDAwMDAA8BAQEBAQEBAQEBAQEBAQEDAwEBAQEDAwICA/kDAwMAAAgAAAIADgAQAADMAOwAAATU7AR0BKwEdATsBPQE7AT0BOwEdASsBHQE7AR0BOwEdASsBHQEpAT0BKwE9ATsBPQE7AQE1KwEdATsBAQBAQEBAgIBAQEBAQEBAQEBAQED+wP7AQEBAQEBAAYDAwMDAA8BAQEBAQEBAQEBAQEBAQEDAwEBAQEDAwICA/kDAwMAAAgAAAIADgAQAAB8AJwAAEzUpAR0BKwEdATsBHQErAR0BOwEdASkBPQErARkBOwEBESsBGQE7AYABgAGAgICAgICAgID+gP6AQEBAQAEAQEBAQAPAQEBAgIBAQICAQEBAQAFAAUD+wAFA/sD+wAAAAAMAAACAA4ADAAAbACMAKwAAEzUpAR0BOwEdASsBHQE7AR0BKQE9ASsBPQE7AQU1KwEdATsBATUrAR0BOwGAAUABQEBAwMCAgP7A/sBAQEBAAQBAQEBAAQBAQEBAAsBAQECAgEBAQEBAQMDAwMDAwAFAQEBAAAAAAAIAAACAA4AEAAAzADsAAAE1OwEdASsBHQE7AR0BOwEdASsBHQE7AR0BOwEdASsBPQErAT0BKwEdASsBGQE7AT0BOwERNSsBHQE7AQIAQEBAQICAQECAgEBAQEDAwEBAQECAgMDAQECAgICAA8BAQEBAQEBAQEBAQEBAQEBAQEBAgIABQAFAQED+wEBAQAAAAAIAgACAA4AEAAAfACcAAAE1OwEdASsBHQE7AR0BKwEdASsBHQErARkBOwE9ATsBFTUrAR0BOwECAEBAQEDAwMDAQECAgICAQEBAQEBAA8BAQEBAQEBAQEDAwAFAAUBAQMBAQEAAAAAAAgAAAAADgAQAADMAPwAAGQEpAR0BOwEdASsBHQE7AR0BOwEdASsBHQErAR0BKwE9ATsBPQE7AT0BKwE9ASsBHQErAQE1KwEdATsBPQE7AQGAAYBAQICAQEBAQICAQECAgEBAQEBAQEBAgIACgMDAgIBAQAKAAYBAQICAQEBAQEBAQEBAQEBAQEBAQEBAgIACQECAgEBAAAABAIAAAAOAA4AAJwAAExE7AR0BOwE9ATsBHQErAR0BKwEdATsBHQErAR0BKwE9ATsBPQErAYCAgEBAwMDAwEBAQEBAQICAQEBAQAJAAUBAQEBAQEBAQMDAQEBAQEBAQEAAAAAAAgAAAIADgAQAADsAQwAAATU7AR0BOwE9ATsBHQErAR0BOwEdATsBHQErAR0BOwEdATsBHQErAT0BKwE9ASsBHQErARkBOwE9ASsBATUrAR0BOwEBAEBAQEBAQEBAgIBAQICAQEBAQMDAQEBAQICAwMBAQAEAgICAgAPAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQECAgAFAAUBAQP7AQEBAAAIAgACAA4AEAAAnAC8AAAE1OwEdATsBPQE7AR0BKwEdATsBHQErAR0BKwEdASsBGQE7AT0BKwEFNSsBHQE7AQEAgIBAQICAQEBAQMDAQECAgICAQEABAEBAQEADwEBAQEBAQEBAQEBAQEDAwAFAAUBAQMBAQEAAAAEAAACAA4AEAAA3AAABNTsBHQErAR0BOwEdASkBHQEpAR0BOwEdASsBHQEpAT0BKQE9ASkBPQErAT0BOwE9ATsBPQE7AQIAQEBAQICA/wD/AAEAAQBAQEBA/oD+gAFAAUD/AP8AQEBAQICAQEADwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAQAAAIADgAQAADcAAAE1OwEdASsBHQE7AR0BKQEdASkBHQE7AR0BKwEdASkBPQEpAT0BKQE9ASsBPQE7AT0BOwE9ATsBAgBAQEBAgID/AP8AAQABAEBAQED+gP6AAUABQP8A/wBAQEBAgIBAQAPAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAACAAAAgAOABAAALwA3AAABNTsBHQE7AR0BKQEdASkBHQE7AR0BKwEdASkBPQEpAT0BKQE9ASsBPQE7AT0BOwEFNSsBHQE7AQEAwMBAQP8A/wABAAEAQEBAQP6A/oABQAFA/wD/AEBAQEBAQAEAQEBAQAPAQEBAgIBAQEBAQEBAQEBAQEBAQEBAgIBAQEBAAAACAAAAgAOABAAALwA3AAABNTsBHQE7AR0BKQEdASkBHQE7AR0BKwEdASkBPQEpAT0BKQE9ASsBPQE7AT0BOwEFNSsBHQE7AQEAwMBAQP8A/wABAAEAQEBAQP6A/oABQAFA/wD/AEBAQEBAQAEAQEBAQAPAQEBAgIBAQEBAQEBAQEBAQEBAQEBAgIBAQEBAAAABAAAAAAOABAAAPwAAEzUpAR0BKQEdASkBHQE7AR0BKwEdASsBHQErAR0BKwE9ATsBPQErAT0BKwE9ATsBHQE7AT0BKQE9ASsBPQE7AYABQAFA/wD/AAEAAQBAQEBAQEBAQICAgIDAwEBAgIDAwP8A/wBAQEBAA8BAQEBAQEBAgIBAQEBAQEBAQEBAQEBAQEBAgIBAQEBAAAEAAAAAA4ADgAA3AAATNSkBHQEpAR0BKQEdATsBHQErAR0BKwEdASsBHQErAT0BOwE9ASkBPQEpAT0BKQE9ASsBPQE7AYABQAFA/wD/AAEAAQBAQEBAQEBAQICAgID/AP8AAUABQP8A/wBAQEBAA0BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAQAAAIADgAQAAD8AABM1OwEdATsBPQE7AR0BKwEdATsBHQEpAR0BKQEdATsBHQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQE7AT0BKwGAgIBAQICAQEBAQP8A/wABAAEAQEBAQP6A/oABQAFA/wD/AEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAQAAAIADgAQAAD8AABM1OwEdATsBPQE7AR0BKwEdATsBHQEpAR0BKQEdATsBHQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQE7AT0BKwGAgIBAQICAQEBAQP8A/wABAAEAQEBAQP6A/oABQAFA/wD/AEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAQCAAAADgAQAABsAABM1KQEdASsBGQErAR0BKwE9ATsBPQErARkBKwGAAYABgICAQECAgICAQECAgAPAQEBA/oD+gEBAQEBAQAFAAUAAAAABAIAAAAOABAAAIwAAATU7AR0BOwEdASsBGQErAR0BKwE9ATsBPQErAT0BKwE9ATsBAYCAgICAgIBAQICAgIBAQICAgIADgICAgEBA/wD/AEBAQEBAQMDAQEAAAAEAgACAA4AEAAAnAAABNTsBHQE7AT0BOwEdASsBHQE7AR0BKwEZASsBGQErAT0BOwE9ASsBAQBAQICAQEBAQICAgICAgICAgIBAQAPAQEBAQEBAQEBAQED/AP8AAQABAEBAQEAAAAAAAQCAAIADgAQAACcAAAE1OwEdATsBPQE7AR0BKwEdATsBHQErAR0BKwE9ASsBPQE7AT0BKwEBAEBAgIBAQEBAgICAgICAgICAgEBAA8BAQEBAQEBAgIBAQMDAwMBAQICAAAAAAAEAgACAA4AEAAAfAAATNSkBHQErAR0BOwEdASsBHQErAT0BKwE9ATsBPQErAYABgAGAgIBAQEBAgIBAQEBAgIADwEBAQICAQEDAwICAQEDAwAAAAAEAgACAA4AEAAAnAAABNTsBHQE7AR0BKwEdATsBHQErAR0BKwE9ASsBPQE7AT0BKwE9ATsBAYCAgICAgIBAQEBAgIBAQEBAgICAgAOAgICAQEBAQEBAgIBAQEBAgIBAQAAAAAACAAAAgAOABAAAJwAzAAABNTsBHQE7AT0BOwEdASsBHQE7ARkBKwEdASkBPQErARkBOwE9ATsBFzUrARkBOwEZASsBAQCAgEBAQEBAQICAQED+wP7AQEBAQEBAgEBAwMCAgAPAQEBAQEBAQEBA/wD/AEBAQEABAAEAQEBAQP7A/sABAAEAAAAAAgAAAIADgAQAACMALwAAATU7AR0BOwE9ATsBHQErAR0BOwEZASkBPQErARkBOwE9ATsBFzUrARkBOwEZASsBAQCAgEBAQEBAQICA/oD+gEBAQEBAQIBAQMDAgIADwEBAQEBAQEBAQP7A/sBAQAEAAQBAQEBA/sD+wAEAAQAAAAACAAAAgAOABAAAFwAfAAAZATsBGQE7ARkBOwEZASsBHQEpAT0BKwETNSkBHQEpAYCAwMCAgEBA/sD+wEBAgAFAAUD+wP7AAgABAP8A/wABAAEA/wD/AEBAQEACwEBAQAACAAAAgAOABAAAEwAbAAAZATsBGQE7ARkBOwEZASkBPQErARM1KQEdASkBgIDAwICA/oD+gEBAgAFAAUD+wP7AAgABAP8A/wABAAEA/sD+wEBAAsBAQEAAAgAAAIADgAQAACcALwAAEzU7AR0BOwE9ATsBHQErAR0BOwEZASsBHQEpAT0BKwEZATsBPQErAQERKwEZATsBgEBAwMBAQEBAgIBAQP7A/sBAQICAQEACAMDAwMADwEBAQEBAQEBAQP8A/wBAQEBAAQABAEBA/oABAP8A/wAAAAACAAAAgAOABAAAIwArAAATNTsBHQE7AT0BOwEdASsBHQE7ARkBKQE9ASsBGQE7AT0BKwEBESsBGQE7AYBAQMDAQEBAQICA/oD+gEBAgIBAQAIAwMDAwAPAQEBAQEBAQEBA/sD+wEBAAQABAEBA/oABAP8A/wAAAAADAAAAgAOABAAAHwAnAD8AAAE1OwEdATsBHQE7ARkBKwEdASkBPQErARkBOwE9ATsBBTUrAR0BOwEFNSsBHQE7AR0BOwE9ATsBPQErAR0BKwEBAMDAQEBAQEBA/sD+wEBAQEBAQAEAQEBAQP8AQEBAQMDAQEBAQMDAA8BAQEBAQP8A/wBAQEBAAQABAEBAQEBAQEBAQEDAwMDAQEBAQAAAAwAAAIADgAQAABsAIwA7AAABNTsBHQE7AR0BOwEZASkBPQErARkBOwE9ATsBBTUrAR0BOwEFNSsBHQE7AR0BOwE9ATsBPQErAR0BKwEBAMDAQEBAQP6A/oBAQEBAQEABAEBAQED/AEBAQEDAwEBAQEDAwAPAQEBAQED+wP7AQEABAAEAQEBAQEBAQEBAQMDAwMBAQEBAAAABAAAAgAOABAAAMwAAATU7AR0BKwEZATsBGQErAT0BOwE9ATsBHQErAR0BOwEZASsBHQEpAT0BKwEZATsBPQE7AQEAQEBAQMDAQEBAQEBAQECAgEBA/sD+wEBAQEBAQAPAQEBA/sD+wAEAAQBAQEBAQEBAQP8A/wBAQEBAAQABAEBAAAABAAAAgAOABAAALwAAATU7AR0BKwEZATsBGQErAT0BOwE9ATsBHQErAR0BOwEZASkBPQErARkBOwE9ATsBAQBAQEBAwMBAQEBAQEBAQICA/oD+gEBAQEBAQAPAQEBA/sD+wAEAAQBAQEBAQEBAQP7A/sBAQAEAAQBAQAAAAQAAAAADgAQAAC8AABkBOwEZATsBGQE7ARkBKwEdASsBHQErAR0BOwEdASsBPQErAT0BOwE9ASsBPQErAYCAwMCAgEBAgIBAQICAgIBAQEBAgIBAQAMAAQD/AP8AAQABAP8A/wBAQEBAQEBAQEBAQEBAQEBAAAAAAQAAAAADgAQAACsAABkBOwEZATsBGQE7ARkBKwEdASsBHQE7AR0BKwE9ASsBPQE7AT0BKwE9ASsBgIDAwICAwMBAQICAgIBAQEBAgIBAQAMAAQD/AP8AAQABAP7A/sBAQEBAQEBAQEBAQEBAQAAAAAIAAACAA4AEAAAfADcAAAE1OwEdATsBHQE7ARkBKwE9ASsBHQErARkBOwE9ATsBBTUrAR0BKwEdATsBPQE7AR0BOwE9ASsBAQDAwEBAQEDAwEBAwMBAQEBAAQBAQEBAQEBAQEBAQEADwEBAQEBA/sD+wEBAQEABQAFAQEBAQEBAgIBAQEBAgIAAAAAAAwAAAIADgAQAABsAKwAzAAABNTsBHQE7AR0BOwEZASkBPQErARkBOwE9ATsBBTUrAR0BOwEZATsBGQErAQERKwEZATsBAQDAwEBAQED+gP6AQEBAQEBAAQBAQEBAQEBAQP8AQEBAQAPAQEBAQED+wP7AQEABAAEAQEBAQEBA/wD/AAEAAQD/AAEA/wD/AAAAAgCAAIADgAQAACcALwAAATU7AR0BOwEdATsBHQErAR0BKwEdASsBPQErAT0BKwE9ATsBPQE7AQU1KwEdATsBAYCAgEBAQEBAQEBAgIBAQEBAQEBAQAEAgICAgAPAQEBAQECAgEBAgICAgEBAgIBAQMDAwMAAAAAAAgAAAAADgAQAACcANwAAATU7AR0BOwEdATsBGQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQE7AQU1KwEdASsBHQE7AT0BKwEBAMDAQEBAQEBA/sD+wAEAAQD/AP8AQEBAQEBAAQBAQEBAwMBAQAPAQEBAQED+wP7AQEBAQEBAQEDAwEBAQEBAQMDAwMAAAAAAAwCAAIADgAQAAB8AJwAvAAATNTsBHQE7AT0BOwEdASsBHQErAR0BKwE9ASsBPQErAQE1OwEdASsBJTU7AR0BKwGAgICAgICAQEBAQICAQEBAQAIAgICAgP4AgICAgAKAgICAgICAgEBAgICAgEBAAcBAQEBAQEBAAAAAAAEAAACAA4AEAAA3AAABNTsBHQErAR0BOwEdASsBHQErAR0BKwEdATsBHQEpAT0BOwE9ATsBPQE7AT0BKwE9ATsBPQE7AQIAQEBAQMDAQEBAQEBAwMD+QP5AQEBAQEBAwMDAwEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAEAAACAA4AEAAA3AAABNTsBHQErAR0BOwEdASsBHQErAR0BKwEdATsBHQEpAT0BOwE9ATsBPQE7AT0BKwE9ATsBPQE7AQIAQEBAQMDAQEBAQEBAwMD+QP5AQEBAQEBAwMDAwEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAIAAACAA4AEAAAnAC8AABE1KQEdASsBHQErAR0BKwEdATsBHQEpAT0BOwE9ATsBPQE7AT0BKwEBNTsBHQErAQHAAcBAQEBAQEDAwP5A/kBAQEBAQEDAwAGAgICAgALAQEBAQEBAQEBAQEBAQEBAQEBAQAFAQEBAAAIAAACAA4AEAAAnAC8AABE1KQEdASsBHQErAR0BKwEdATsBHQEpAT0BOwE9ATsBPQE7AT0BKwEBNTsBHQErAQHAAcBAQEBAQEDAwP5A/kBAQEBAQEDAwAGAgICAgALAQEBAQEBAQEBAQEBAQEBAQEBAQAFAQEBAAAEAAACAA4AEAAA/AAATNTsBHQE7AT0BOwEdASsBHQE7AR0BKwEdASsBHQErAR0BOwEdASkBPQE7AT0BOwE9ATsBPQErAT0BOwE9ASsBgICAQECAgEBAgIBAQEBAQEDAwP5A/kBAQEBAQEDAwICAQEADwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAABAAAAgAOABAAAPwAAEzU7AR0BOwE9ATsBHQErAR0BOwEdASsBHQErAR0BKwEdATsBHQEpAT0BOwE9ATsBPQE7AT0BKwE9ATsBPQErAYCAgEBAgIBAQICAQEBAQEBAwMD+QP5AQEBAQEBAwMCAgEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAQGAAIADgAQAAA8AAAE1OwEdASsBGQErARkBOwECAMDAgICAgEBAA8BAQED+gP6AAYABgAAAAAABAIAAgAOABAAAJwAAATU7AR0BKwEdATsBHQErAR0BKwEdASsBPQE7AT0BKwE9ATsBPQE7AQIAwMCAgICAgIBAQMDAgICAgICAQEADwEBAQEBAQEDAwEBAQEDAwEBAQEAAAAAAAQCAAwADAAQAABcAAAE1OwEdATsBHQErAT0BKwEdASsBPQE7AQEAwMBAQICAQECAgEBAA8BAQEBAQEBAQEBAQAAAAAABAIADAAMABAAAFwAAEzU7AR0BOwE9ATsBHQErAR0BKwE9ASsBgICAQECAgEBAwMBAQAPAQEBAQEBAQEBAQEAAAQCAA4ADAAQAAAcAABM1KQEdASkBgAFAAUD+wP7AA8BAQEAAAQGAAwACgAQAAA8AAAE1OwEdASsBHQErAT0BOwECAEBAQEBAQEBAA8BAQEBAQEBAAAAAAAEBgAMAAoAEAAAPAAABNTsBHQE7AR0BKwE9ASsBAYBAQEBAQEBAQAPAQEBAQEBAQAAAAAABAIACAAMAAoAABwAAEzUpAR0BKQGAAUABQP7A/sACQEBAQAABAIADAAMABAAAFwAAEzU7AR0BOwE9ATsBHQErAR0BKwE9ASsBgEBAwMBAQEBAwMBAQAPAQEBAQEBAQEBAQEAAAQGAA4ACgAQAAAcAAAE1OwEdASsBAYCAgICAA8BAQEAAAAAAAgCAAoADAAQAABcAHwAAATU7AR0BOwEdASsBHQErAT0BKwE9ATsBBTUrAR0BOwEBAMDAQEBAQMDAQEBAQAEAQEBAQAPAQEBAQEBAQEBAQEBAQEBAAAAAAAEBAAAAAoABgAAXAAABNTsBHQErAR0BOwEdASsBPQErAT0BOwEBgEBAQECAgICAQEBAQAFAQEBAQEBAQEBAQEAAAAAAAQCAAwADAAQAAB8AAAE1OwEdATsBPQE7AR0BKwEdASsBPQErAR0BKwE9ATsBAQCAgEBAQEBAQICAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEAAAAAAAgCAAwADAAQAAA8AHwAAATU7AR0BKwEdASsBPQE7ASU1OwEdASsBHQErAT0BOwECgEBAQEBAQEBA/oBAQEBAQEBAQAPAQEBAQEBAQEBAQEBAQEBAAAAAAAEBgAAAAwABAAAPAAAlNTsBHQE7AR0BKwE9ASsBAYBAQICAgIBAQMBAQEBAQEBAAAIAgACAAgADgAAPABcAAAE1OwEdASsBHQErAT0BOwERNTsBHQErAQEAgIBAQICAQECAgICAAYCAgIBAQEBAAgCAgIAAAQGAAwACgAQAAA8AAAE1OwEdASsBHQErAT0BOwECAEBAQEBAQEBAA8BAQEBAQEBAAAAAAAMAgAMAA4AEAAAHABcAHwAAATU7AR0BKwElNTsBHQErAR0BKwE9ATsBJTU7AR0BKwEDAEBAQED/AEBAQEBAQEBA/oBAQEBAA8BAQEBAQEBAQEBAQEBAQEAAAAAAAwAAAIADgAQAACcALwA3AAATNTsBHQE7AT0BOwEdATsBHQE7ARkBKwE9ASsBHQErARkBKwE9ATsBFzUrAR0BOwEFNSsBHQE7AYBAQEBAgIBAQEBAgICAgICAQEBAQIBAQEBAAYCAgICAA8BAQEBAQEBAQED+wP7AgICAgAFAAUBAQEBAQECAgICAAAABAQABgAIAAoAABwAAATU7AR0BKwEBAICAgIACAICAgAAAAAABAAAAgAQABAAAIwAAEzUpAR0BKQEdATsBHQErAR0BKQEdASkBGQErAR0BKwE9ATsBgAHAAcD/AP8AgICAgAEAAQD+gP6AQEBAQEBAA8BAQECAgEBAgIBAQAGAAYBAQEBAAAAAAQAAAIAEAAQAACMAABM1OwEdATsBPQE7ARkBKwE9ASsBHQErARkBKwEdASsBPQE7AYDAwICAgICAgICAgIBAQEBAQEADwEDAwMDA/kD+QMDAwMABgAGAQEBAQAACAAAAgAOABAAAFwAnAAABNSkBHQErARkBOwEdASkBPQE7ARkBKwElNTsBHQErAR0BKwE9ATsBAYABAAEAQEBAQP8A/wBAQEBA/wBAQEBAQEBAQAPAQEBA/sD+wEBAQEABQAFAQEBAQEBAQEAAAAAAAgAAAIAEAAQAACcALwAAEzU7AR0BOwE9ASkBHQE7ARkBKwEdASkBPQErARkBKwEdASsBPQE7AQERKwEZATsBgEBAQEABAAEAQEBAQP8A/wBAQEBAQEBAQAKAgICAgAPAQEBAQEBAQP7A/sBAQEBAAUABQEBAQED+wAFA/sD+wAABAAAAgAQABAAAKwAAEzU7AR0BOwE9ATsBHQErAR0BKwEdASsBPQErAT0BKwE9ASsBHQErAT0BOwGAwMCAgICAQEBAQICAQEBAQEBAQEBAQAPAQMDAwMDAwEBAwMDAwEBAgIBAQEBAAAIAAACABAAEAABHAE8AABM1OwEdATsBPQE7AR0BOwEdATsBHQErAR0BOwEdASsBPQE7AT0BKwE9ASsBHQErAR0BOwEdASsBPQE7AT0BKwE9ASsBPQE7ARc1KwEdATsBgEBAQEDAwEBAQEBAQEBAwMBAQEBAQEBAQEBAwMBAQEBAQEBAQIBAQEBAA8BAQEBAQEBAQEDAwEBAQECAgMDAQEBAQMDAgIBAQEBAwMBAQEBAQEAAAAMAAACAAwAEAAAHACsAMwAAATU7AR0BKwElNTsBHQErAR0BOwEZATsBHQErAT0BKwE9ASsBPQE7AT0BOwElNTsBHQErAQKAQEBAQP8AQEBAQEBAgIDAwEBAQEBAQEBA/oBAQEBAA8BAQEBAQEBAQED/AP8AQEBAQMDAQEBAQEBAQEAAAAIAAACAA4AEAAAfAC8AAAE1OwEdATsBHQE7ARkBKwE9ASsBHQErARkBOwE9ATsBBTUrAR0BKwEdATsBPQErAQEAwMBAQEBAgIDAwICAQEBAQAEAQEBAQMDAQEADwEBAQEBA/sD+wICAgIABQAFAQEBAQEBAgICAgAAAAAADAAAAgAOABAAAFwAfACcAABkBKQEdATsBHQErAR0BOwEdASsBHQEpAQE1KwEdATsBETUrAR0BOwEBgAGAQEBAQEBAQED+gP6AAoDAwMDAwMDAwAJAAcBAQICAQECAgEBAAoCAgID/AICAgAABAIAAgAOABAAACwAAExEpAR0BKQEZASsBgAGAAYD/AP8AgIACQAHAQED+gP6AAAACAAAAgAOABAAAFwAnAAABNTsBHQE7AR0BOwEZASkBGQE7AT0BOwEFNSsBHQErARkBOwEZASsBAQDAwEBAQED+QP5AQEBAQAEAQEBAQMDAQEADwEBAQEBA/sD+wAFAAUBAQEBAQED/AP8AAQABAAAAAQAAAIADgAQAABcAABkBKQEdASkBHQEpAR0BKQEdASkBHQEpAQHAAcD+wP7AAQABAP8A/wABQAFA/kD+QAJAAcBAQICAQECAgEBAAAEAAACAA4AEAAAvAAARNSkBHQErAR0BKwEdASsBHQErAR0BKQEdASkBPQE7AT0BOwE9ATsBPQE7AT0BKQEBwAHAQEBAQEBAQEABAAEA/kD+QEBAQEBAQEBA/wD/AAPAQICAQEBAQEBAQEBAQICAQEBAQEBAQEAAAAEAAACAA4AEAAAXAAAZATsBHQE7AT0BOwEZASsBPQErAR0BKwGAgMDAgICAgMDAgIACQAHAwMDAwP5A/kDAwMDAAAAAAwAAAIADgAQAABcAHwAnAAATNSkBHQE7ARkBKwEdASkBPQErARkBOwEFNSsBHQE7ARE1KwEdATsBgAFAAUBAQEBA/sD+wEBAQEACAMDAwMDAwMDAA8BAQED+wP7AQEBAQAFAAUCAgICA/wCAgIAAAAEAgACAA4AEAAAXAAATNSkBHQErARkBOwEdASkBPQE7ARkBKwGAAYABgICAgID+gP6AgICAgAPAQEBA/sD+wEBAQEABQAFAAAEAAACAA4AEAAA7AAAZATsBHQE7AT0BOwE9ATsBPQE7AR0BKwEdASsBHQErAR0BOwEdATsBHQE7AR0BKwE9ASsBPQErAR0BKwGAgEBAQEBAQICAQEBAQEBAQEBAQEBAwMBAQEBAgIACQAHAwMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQECAgAABAAAAgAOABAAAJwAAATU7AR0BOwEdATsBGQErARkBKwE9ASsBHQErARkBKwEZATsBPQE7AQEAwMBAQEBAgIBAQEBAQECAgEBAQEADwEBAQEBA/sD+wAFAAUBAQEBA/sD+wAFAAUBAQAAAAAABAAAAgAOABAAAJwAAGQE7AR0BOwEdATsBPQE7AT0BOwEZASsBPQErAR0BKwE9ASsBHQErAYCAQEBAQEBAgICAgEBAQEBAQICAAkABwEBAQEBAQEBA/kD+QMDAQEBAQMDAAAAAAQAAAIADgAQAACcAABkBOwEdATsBHQE7AR0BOwE9ATsBGQErAT0BKwE9ASsBPQErAR0BKwGAgEBAQEBAQICAgIBAQEBAQECAgAJAAcBAQEBAQEDAwP5A/kBAQEBAQEDAwAAAAAMAAACAA4AEAAAHAA8AFwAAPQEpAR0BKQETNSkBHQEpAQM1KQEdASkBAcABwP5A/kCAAUABQP7A/sCAAcABwP5A/kDAQEBAAcBAQEABwEBAQAAAAAIAAACAA4AEAAAXAB8AABM1KQEdATsBGQErAR0BKQE9ASsBGQE7AQERKwEZATsBgAFAAUBAQEBA/sD+wEBAQEACAMDAwMADwEBAQP7A/sBAQEBAAUABQP7AAUD+wP7AAAEAAACAA4AEAAAPAAAZASkBGQErARkBKwEZASsBAcABwICAwMCAgAJAAcD+QP5AAYABgP6A/oAAAgAAAIADgAQAABMAGwAAGQEpAR0BOwEdASsBHQEpAR0BKwEBNSsBHQE7AQGAAYBAQEBA/wD/AICAAoDAwMDAAkABwEBAwMBAQICAAkDAwMAAAAAAAQAAAIADgAQAAD8AABE1KQEdASsBPQErAR0BOwEdATsBHQErAR0BKwEdATsBPQE7AR0BKQE9ATsBPQE7AT0BOwE9ASsBPQErAT0BKwEBwAHAgICAgEBAgICAgEBAgICAgP5A/kBAQEBAQEBAQEBAQEADwECAgEBAQEBAQEBAQEBAQEBAgIBAQEBAQEBAQEBAQEAAAAEAgACAA4AEAAAPAAATNSkBHQErARkBKwEZASsBgAGAAYCAgICAgIADwEBAQP6A/oABgAGAAAAAAQCAAIADgAQAAB8AABM1OwEdATsBPQE7AR0BKwEdASsBHQErAT0BKwE9ASsBgICAgICAgEBAQECAgEBAQEADQMDAwMDAwMBAQMDAwMBAQAADAAAAgAOABAAAJwAvADcAAAE1OwEdATsBHQE7AR0BKwEdASsBHQErAT0BKwE9ASsBPQE7AT0BOwERNSsBHQE7ASU1KwEdATsBAYBAQICAQEBAQICAQECAgEBAQECAgEBAQEABAEBAQEADwEBAQEBAwMBAQEBAQEBAQMDAQED+wMDAwMDAwMAAAQAAAIADgAQAAEcAABE1OwEdATsBHQE7AT0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBPQErAR0BKwEdASsBPQE7AT0BOwE9ASsBPQErAYCAQEBAQEBAgIBAQEBAQEBAQICAQEBAQEBAgIBAQEBAQEBAQAOAgEBAQEBAQEBAgIBAQEBAQECAgEBAQEBAQEBAgIBAQEBAQEAAAAEAAACABAAEAAAvAAARNTsBHQE7AT0BOwEdATsBPQE7AR0BKwEdASsBHQE7AR0BKQE9ATsBPQErAT0BKwGAgEBAgIBAQICAQECAgEBA/wD/AEBAgIBAQANAwMDAwMDAwMDAwMBAQICAQEBAQICAQEAAAAAAAQAAAIADgAQAAD8AAAE1OwEdATsBHQE7AR0BKwEdATsBHQErAT0BOwE9ASsBPQErAR0BKwEdATsBHQErAT0BOwE9ASsBPQE7AT0BOwEBAMDAQEBAQEBAQEDAwEBAQEBAQEBAQEDAwEBAQEBAQEBAA8BAQEBAQMDAQEBAQICAwMBAQEBAwMCAgEBAQEDAwEBAAAAAAAMAgACAA4AEAAAXAB8AJwAAEzUpAR0BKwEdATsBHQEpAT0BOwE9ASsBATU7AR0BKwElNTsBHQErAYABgAGAgICAgP6A/oCAgICAAgCAgICA/gCAgICAAsBAQEDAwEBAQEDAwAFAQEBAQEBAQAAAAAADAIAAgAOABAAAHwAnAC8AABM1OwEdATsBPQE7AR0BKwEdASsBHQErAT0BKwE9ASsBATU7AR0BKwElNTsBHQErAYCAgICAgIBAQEBAgIBAQEBAAgCAgICA/gCAgICAAoCAgICAgICAQECAgICAQEABwEBAQEBAQEAAAAAAAgAAAIADgAQAAB8AJwAAATU7AR0BKwEdATsBGQEpAT0BKwE9ATsBPQE7AT0BOwETNSsBHQE7AQIAQEBAQMDA/oD+gEBAQECAgEBAgMDAwMADwEBAQEBA/sD+wEBAwMBAQEBA/kDAwMAAAAAAAQAAAIADgAQAADcAAAE1OwEdASsBHQE7AR0BKQEdASkBHQEpAR0BKQEdASkBPQErAT0BOwE9ASsBPQE7AT0BOwE9ATsBAgBAQEBAwMD+wP7AAQABAP8A/wABQAFA/oD+gEBAQEBAQEBAgIBAQAPAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAABAAAAAAOABAAAKwAAATU7AR0BKwEdATsBHQE7ARkBKwEZASsBGQErARkBKwE9ATsBHQE7AT0BOwECAEBAQECAgEBAgICAgICAQECAgEBAQEADwEBAQEBAQED+wP7AAUABQP8A/wABAAEAQEBAQICAAAAAAAEAgACAAwAEAAAjAAABNTsBHQErAR0BOwEZATsBHQErAT0BKwE9ASsBPQE7AT0BOwEBgEBAQEBAQICAwMBAQEBAQEBAQAPAQEBAQED/AP8AQEBAQMDAQEBAQAAAAwAAAIADgAQAAAcAPwBHAAABNTsBHQErASU1OwEdASsBHQErAR0BOwEdATsBPQErAT0BOwEdATsBHQErAR0BKQE9ASsBPQErAT0BOwE9ATsBJTU7AR0BKwECgEBAQED/AEBAQEBAQEBAgIBAQICAQEBAQP8A/wBAQEBAgIBAQP6AQEBAQAPAQEBAQEBAQEBAQEDAwMDAQEBAQMDAQEBAQMDAQEBAQEBAQEAAAAIAAACAA4ADAAAPABcAABM1KQEZASkBPQErAT0BOwEFNSsBHQE7AYABgAGA/oD+gEBAQEACAMDAwMACwED+wP7AQEDAwMDAwMAAAAADAAAAAAOABAAAHwAnAC8AABM1KQEdATsBHQErAR0BOwEdASsBHQEpAR0BKwEZATsBBTUrAR0BOwERNSsBHQE7AYABQAFAQEBAQEBAQED/AP8AgIBAQAIAwMDAwMDAwMADwEBAQICAQECAgEBAQEABwAHAgICAgP8AgICAAAAAAAEAAAAAA4ADAAAnAAARNTsBHQE7AR0BOwE9ATsBHQErAR0BKwEdASsBPQErAT0BKwE9ASsBgIBAQICAgIBAQEBAgIBAQEBAQEACwEBAQICAwMDAwEBAgICAgEBAgIAAAAIAAACAA4AEAAArADMAAAE1OwEdASsBHQE7AR0BOwEdASsBHQEpAT0BKwE9ATsBPQE7AT0BKwE9ATsBEzUrAR0BOwECAMDAgIBAQEBAQED+wP7AQEBAQMDAQEBAQIDAwMDAA8BAQEBAQEBAwMBAQEBAgIBAQEBAQED+AICAgAAAAQAAAIADgAMAACcAABM1KQEdASkBHQEpAR0BKQEdASkBHQEpAT0BKwE9ATsBPQErAT0BOwGAAYABgP7A/sABAAEA/wD/AAFAAUD+gP6AQEBAQEBAQEACwEBAQEBAQEBAQEBAQEBAQEBAQEAAAQAAAAADgAQAADcAABM1KQEdASsBHQErAR0BKwEdASkBHQE7AR0BKwEdASsBPQE7AT0BKQE9ASsBPQE7AT0BOwE9ASsBgAFAAUCAgEBAQEABAAEAQEBAQICAQED/AP8AQEBAQEBAQEADwEBAQEBAQECAgEBAQEBAQEBAQEBAQICAQEBAQAAAAAEAAAAAA4ADAAAfAAARNTsBHQE7AT0BOwEdATsBGQErARkBKwEZASsBGQErAYCAQEDAwEBAgICAgICAQEACwEBAQEBAQED+wP7AAUABQP8A/wABAAEAAAADAAAAgAOABAAAJwA3AEcAAAE1OwEdATsBHQE7AR0BKwEdASsBHQErAT0BKwE9ASsBPQE7AT0BOwEFNSsBHQErAR0BOwE9ASsBEzUrAR0BOwEdATsBPQE7AQEAwMBAQEBAQEBAQMDAQEBAQEBAQEABAEBAQEDAwEBAgMDAQEBAQEBAA8BAQEBAQMDAQEBAQEBAQEDAwEBAQEBAQEBAQED+wEBAQEBAQEAAAAAAAQCAAIADAAMAABMAABM1OwEZATsBHQErAT0BKwE9ASsBgMDAgIDAwEBAQEACwED/AP8AQEBAQMDAAAAAAQAAAIADgAMAAC8AABkBOwEdATsBPQE7AT0BOwEdASsBHQErAR0BOwEdATsBHQErAT0BKwE9ASsBHQErAYCAgIBAQICAQEBAQEBAQECAgEBAgICAgAHAAUCAgEBAQEBAQEBAQEBAQEBAQEBAQICAAAEAAACAA4AEAAA7AAARNTsBHQE7AR0BOwEdATsBHQE7AR0BOwEdASsBPQErAT0BKwEdASsBHQErAT0BOwE9ATsBPQErAT0BKwGAgEBAQEBAQEBAQECAgEBAQEBAQICAQEBAQEBAQEADwEBAQEBAQEBAQEBAgICAgEBAQECAgICAQECAgEBAAAABAAAAAAOAAwAAHwAAGQE7ARkBOwEZATsBGQE7AR0BKwE9ASsBHQErAR0BKwGAgICAgIBAQICAQECAgICAAYABgP8A/wABAAEA/wD/AEBAQEBAQEBAAAAAAQAAAIADgAMAACcAABE1OwEdATsBHQE7AT0BOwEdASsBHQErAR0BKwE9ASsBPQErAT0BKwGAgEBAgICAgEBAQECAgEBAQEBAQALAQEBAgIDAwMDAQEBAQEBAQECAgAAAAQAAAAADAAQAAD8AABE1KQEdASsBHQE7AR0BKwEdASsBHQE7AR0BOwEdASsBHQErAT0BOwE9ASsBPQErAT0BOwE9ATsBPQErAT0BKwEBgAGAwMCAgICAQEDAwEBAQECAgEBAwMBAQEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAIAAACAA4ADAAAXAB8AABM1KQEdATsBHQErAR0BKQE9ASsBPQE7AQU1KwEdATsBgAFAAUBAQEBA/sD+wEBAQEACAMDAwMACwEBAQMDAQEBAQMDAwMDAwAABAAAAgAOAAwAAIwAAETUpAR0BKwEdATsBHQErAT0BKwE9ASsBGQErAT0BOwE9ASsBAcABwEBAQECAgEBAQEDAwEBAQEACwEBAQMDAQEBAQMDA/wD/AEBAwMAAAAIAAAAAA4ADAAAXAB8AABM1KQEdATsBHQErAR0BKQEdASsBGQE7AQU1KwEdATsBgAFAAUBAQEBA/wD/AICAQEACAMDAwMACwEBAQICAQECAgAFAAUCAgICAAAAAAQAAAAADgAMAACcAABM1KQEdASkBHQEpAR0BOwEdASsBHQEpAT0BKQE9ASkBPQErAT0BOwGAAUABQP8A/wABAAEAQEBAQP7A/sABAAEA/wD/AEBAQEACwEBAQICAQEBAQEBAQEBAQEBAgIAAAgAAAIADgAMAABsAJwAAEzUpAR0BKwEdATsBHQErAR0BKQE9ASsBPQE7AQU1KwEdATsBPQErAYABgAGAgIBAQEBA/wD/AEBAQEABAEBAgIBAQALAQEBAQECAgEBAQEDAwEBAwMCAgAABAIAAgAOAAwAAFwAAEzUpAR0BKwEdATsBHQErAT0BKwE9ASsBgAGAAYCAgICAwMBAQICAAsBAQEDAwEBAQEDAwAAAAAEAAACAA4ADAAAnAAARNTsBHQE7AR0BOwE9ASsBPQE7AR0BOwEdASsBHQEpAT0BKwE9ASsBgIBAQICAQECAgEBAQED/AP8AQEBAQALAQEBAwMDAwEBAQEDAwEBAQEDAwAAAAAACAAAAAAOAAwAAKwAzAAATNTsBGQE7AT0BOwE9ATsBHQE7AR0BKwEdASsBHQErAT0BKwE9ASsBPQE7AQU1KwEdATsBgEBAQEBAQICAQEBAQICAQECAgEBAQEACAEBAQEACwED/AP8AwMBAQEBAwMBAQEBAQEBAQMDAwMDAwAAAAAEAAAAAA4ADAABHAAARNTsBHQE7AR0BOwE9ATsBPQE7AR0BKwEdASsBHQE7AR0BOwEdASsBPQErAT0BKwEdASsBHQErAT0BOwE9ATsBPQErAT0BKwGAgEBAQEBAQICAQEBAQEBAQECAgEBAQEBAQICAQEBAQEBAQEACwEBAQEBAQEBAQEBAQEBAQEBAgICAgEBAQECAgICAQEBAQEBAAAABAAAAAAQAA4AAJwAAARE7ARkBOwEZATsBGQErAR0BKwEdASsBPQErAT0BKwEZATsBGQE7AQGAgIBAQICAQECAgICAgIBAQICAQEACQAFA/sD+wAEAAQD/AP8AQEBAQEBAQEABAAEA/wD/AAABAAAAgAOAAwAALwAAEzU7ARkBOwE9ATsBHQE7ARkBOwEdATsBHQErAR0BKwE9ASsBHQErAT0BKwE9ATsBgEBAQEBAQEBAQEBAQEBAgIBAQICAQEBAQALAQP8A/wDAwMDAAQABAEBAwMBAQEBAQEBAQMDAAAMAAACAAwAEAAATABsAIwAAEzU7ARkBOwEdASsBPQErAT0BKwEBNTsBHQErASU1OwEdASsBgMDAgIDAwEBAQEABAICAgID+gICAgIACwED/AP8AQEBAQMDAAUBAQEBAQEBAAAADAAAAgAOABAAAJwAvADcAABE1OwEdATsBHQE7AT0BKwE9ATsBHQE7AR0BKwEdASkBPQErAT0BKwEBNTsBHQErASU1OwEdASsBgIBAQICAQECAgEBAQED/AP8AQEBAQAIAgICAgP6AgICAgALAQEBAwMDAwEBAQEDAwEBAQEDAwAFAQEBAQEBAQAAAAAIAAACAA4AEAAAnAC8AAAE1OwEdASsBHQE7AR0BOwEdASsBHQEpAT0BKwE9ATsBPQE7AT0BOwETNSsBHQE7AQIAQEBAQICAQEBAQP7A/sBAQEBAgIBAQIDAwMDAA8BAQEBAQEBAwMBAQEBAwMBAQEBA/kDAwMAAAAEAAACAA4AEAAA3AAABNTsBHQErAR0BOwEdATsBHQErAR0BKQE9ASsBPQErAT0BOwEdATsBHQE7AT0BKwE9ASsBPQE7AQIAQEBAQICAQEBAQP8A/wBAQEBAgIBAQICAQEBAQEBAA8BAQEBAQEBAwMBAQEBAwMBAQEBAwMDAwEBAQEAAAAIAAACAA4AEAAAvAD8AABM1OwEZATsBPQE7AR0BOwEZATsBHQE7AR0BKwEdASsBPQErAR0BKwE9ASsBPQE7AQE1OwEdASsBHQErAT0BOwGAQEBAQEBAQEBAQEBAQECAgEBAgIBAQEBAAYBAQEBAQEBAQALAQP8A/wDAwMDAAQABAEBAwMBAQEBAQEBAQMDAAUBAQEBAQEBAAAAAAAEAAACAA4AEAAAnAAABNTsBHQE7AR0BOwEdASkBHQEpAR0BKQEdASkBHQEpARkBOwE9ASsBAQBAQEBAwMD+wP7AAQABAP8A/wABQAFA/kD+QMDAQEADwEBAQEBAQEBAQEBAQEBAQAFAAUBAQAAAAAADAAAAgAOABAAAFwAfACcAABkBKQEdASkBHQEpAR0BKQEdASkBHQEpAQE1OwEdASsBJTU7AR0BKwEBwAHA/sD+wAEAAQD/AP8AAUABQP5A/kACAICAgID+gICAgIABwAFAQEBAQEBAQEBAQANAQEBAQEBAQAAAAAABAAAAAAOABAAAJwAAETUpAR0BKwEdATsBHQE7ARkBKwEdASsBPQE7ARkBKwEZASsBGQErAQFAAUCAgMDAQEBAQICAQECAgICAQEADwEBAQEBAQED/AP8AQEBAQAEAAQD/AP8AAYABgAAAAAABAIAAgAOABAAAGwAAATU7AR0BKwEdATsBHQEpARkBKwEZATsBPQE7AQIAQEBAQMDA/wD/AICAgIBAQAPAQEBAQEBAQP8A/wABQAFAQEAAAAEAAACAA4AEAAA/AAABNSkBHQE7AR0BKwE9ASsBHQErAR0BKQEdASkBHQE7AR0BOwE9ATsBHQErAR0BKQE9ASsBPQErAT0BOwE9ATsBAQABAAEAQECAgICAQEABAAEA/wD/AEBAgICAgEBA/wD/AEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwEBAAAAAAAEAAACAA4AEAAA3AAATNSkBHQE7AR0BKwE9ASsBHQEpAR0BOwEdASsBHQEpAT0BKwE9ATsBHQE7AT0BKQE9ASsBPQE7AYABAAEAQECAgICAAQABAEBAQED+wP7AQECAgMDA/wD/AEBAQEADwEBAQEBAQECAgEBAgIBAQEBAQEBAQICAQECAgAABAIAAgAOABAAAFwAAEzUpAR0BKwEZATsBHQEpAT0BOwEZASsBgAGAAYCAgICA/oD+gICAgIADwEBAQP7A/sBAQEBAAUABQAADAIAAgAOABAAAFwAfACcAABM1KQEdASsBHQE7AR0BKQE9ATsBPQErAQE1OwEdASsBJTU7AR0BKwGAAYABgICAgID+gP6AgICAgAIAgICAgP4AgICAgALAQEBAwMBAQEBAwMABQEBAQEBAQEAAAAAAAQAAAIADgAQAABcAAAEROwEZASsBHQEpAT0BKwE9ATsBHQE7AQKAgIBAQP7A/sBAQICAwMACgAGA/oD+gEBAQEBAQEBAAAAAAgAAAIADgAQAACMAKwAAATU7AR0BOwEdATsBHQErAR0BKwEZASsBGQErARkBOwE9ATsBATUrAR0BOwEBAICAgIBAQEBAwMBAQICAQEBAQAGAQEBAQAPAQICAQEDAwEBAAUABQP7A/sABQAFAQED+QMDAwAACAAAAgAOABAAAIwArAAAZATsBHQE7AT0BOwEdATsBHQE7AR0BKwEdASsBPQErAR0BKwEBNSsBHQE7AYCAQEBAQICAQEBAQMDAQECAgAKAQEBAQAJAAcDAwMDAgIBAQMDAQEDAwMDAAUDAwMAAAAAAAQAAAIADgAQAAB8AABE1KQEdASsBHQE7AR0BOwEZASsBGQErARkBKwEZASsBAUABQICAwMBAQICAgICAgEBAA8BAQEBAQEBA/wD/AAEAAQD/AP8AAYABgAAAAAACAAAAgAOABAAALwA/AAAZATsBHQE7AT0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBPQErAR0BKwEBNTsBHQErAR0BKwE9ATsBgICAgEBAgIBAQEBAQEBAQICAQECAgICAAgBAQEBAQEBAQAHAAUCAgEBAQEBAQEBAQEBAQEBAQEBAQICAA0BAQEBAQEBAAAAAAAEAAACAA4AEAAAvAAABNTsBHQE7AR0BOwEZASsBPQErAR0BKwEdASsBGQE7AR0BOwE9ATsBPQErAT0BKwEBAEBAQEDAwICAQEBAQMDAgIBAQEBAQEBAQAPAQEBAQED+wP7AgIBAQEBAAUABQICAQEBAQEBAAAAAAAIAAACAA4AEAAAvADcAABM1OwEdATsBPQE7AR0BKwEdATsBGQErAR0BKQE9ASkBPQEpAT0BKwE9ATsBPQErAQE1KwEdATsBgEBAwMBAQEBAgIBAQP6A/oABQAFA/wD/AEBAgIBAQAIAwMDAwAPAQEBAQEBAQEBA/wD/AEBAQEBAQEBAgIBAQP8AgICAAAAAAAEAAAAAA4AEAAAXAAAZATsBGQE7ARkBOwEZASsBHQErAT0BKwGAgMDAgIDAwEBAwMACQAHA/oD+gAGAAYD+QP5AQEBAQAAAAAIAAACAA4AEAAAfAC8AAAE1OwEdATsBHQE7ARkBKwE9ASsBHQErARkBOwE9ATsBBTUrAR0BKwEdATsBPQErAQEAwMBAQEBAgIDAwICAQEBAQAEAQEBAQMDAQEADwEBAQEBA/sD+wICAgIABQAFAQEBAQEBAgICAgAAAAAACAAAAgAOABAAAFwAfAAAZASkBHQEpAR0BKQEdATsBHQErAR0BKQEBNSsBHQE7AQGAAYD/AP8AAQABAEBAQED+gP6AAoDAwMDAAkABwEBAgIBAQICAQEABAICAgAAAAAADAAAAgAOABAAAFwAfACcAABkBKQEdATsBHQErAR0BOwEdASsBHQEpAQE1KwEdATsBETUrAR0BOwEBgAGAQEBAQEBAQED+gP6AAoDAwMDAwMDAwAJAAcBAQICAQECAgEBAAoCAgID/AICAgAABAIAAgAOABAAACwAAExEpAR0BKQEZASsBgAGAAYD/AP8AgIACQAHAQED+gP6AAAACAAAAAAQABAAAHwArAAABNSkBGQE7AR0BKwE9ASkBHQErAT0BOwE9ATsBPQE7AQERKwEdASsBHQE7AQGAAQABAEBAgID/AP8AgIBAQEBAQEABAEBAQECAgAPAQP5A/kBAQEBAQECAgMDAgID+wAFAgIDAwAAAAAABAAAAgAOABAAAFwAAGQEpAR0BKQEdASkBHQEpAR0BKQEdASkBAcABwP7A/sABAAEA/wD/AAFAAUD+QP5AAkABwEBAgIBAQICAQEAAAQAAAIADgAQAAEcAABE1OwEdATsBPQE7AR0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBHQErAT0BKwEdASsBPQE7AT0BOwE9ASsBPQErAYCAQEBAQEBAgIBAQEBAQEBAQICAQEBAQEBAgIBAQEBAQEBAQAOAgICAgICAgICAgIBAQEBAQECAgICAgICAgICAgIBAQEBAQEAAAAEAAACAA4AEAAA3AAATNSkBHQE7AR0BKwEdATsBHQErAR0BKQE9ASsBPQE7AR0BOwE9ASsBPQE7AT0BKwEdASsBPQE7AYABQAFAQEBAQEBAQED+wP7AQECAgMDAwMDAwMDAgIBAQAPAQEBAgIBAQICAQEBAQEBAQECAgEBAgIBAQEBAAAEAAACAA4AEAAAnAAAZATsBHQE7AT0BOwE9ATsBPQE7ARkBKwE9ASsBHQErAR0BKwEdASsBgIBAQEBAQECAgICAQEBAQEBAgIACQAHAwMBAQEBAQED+QP5AwMBAQEBAQEAAAAACAAAAgAOABAAAKwA3AAATNTsBHQE7AT0BOwEdASsBHQE7ARkBKwE9ASsBHQErAR0BKwEZATsBPQErAQU1KwEdATsBPQE7AYBAQMDAQEBAQICAgIBAQEBAwMCAgEBAAYCAgEBAQEADwEBAQEBAQEBAQP7A/sCAgEBAQEABQAFAQEDAQICAQEAAAQAAAIADgAQAADsAABkBOwEdATsBPQE7AT0BOwE9ATsBHQErAR0BKwEdASsBHQE7AR0BOwEdATsBHQErAT0BKwE9ASsBHQErAYCAQEBAQEBAgIBAQEBAQEBAQEBAQEDAwEBAQECAgAJAAcDAwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQICAAAEAAACAA4AEAAAjAAABNSkBGQErARkBKwEdASsBHQErAR0BKwE9ATsBPQE7AT0BOwEBgAEAAQCAgEBAQEBAQICAQEBAQEBAA8BA/kD+QAGAAYBAQICAwMDAwICAQEAAAAEAAACAA4AEAAAnAAAZATsBHQE7AR0BOwE9ATsBPQE7ARkBKwE9ASsBHQErAT0BKwEdASsBgIBAQEBAQECAgICAQEBAQEBAgIACQAHAQEBAQEBAQED+QP5AwMBAQEBAwMAAAAABAAAAgAOABAAAFwAAGQE7AR0BOwE9ATsBGQErAT0BKwEdASsBgIDAwICAgIDAwICAAkABwMDAwMD+QP5AwMDAwAAAAAIAAACAA4AEAAAXAB8AABM1KQEdATsBGQErAR0BKQE9ASsBGQE7AQERKwEZATsBgAFAAUBAQEBA/sD+wEBAQEACAMDAwMADwEBAQP7A/sBAQEBAAUABQP7AAUD+wP7AAAEAAACAA4AEAAAPAAAZASkBGQErARkBKwEZASsBAcABwICAwMCAgAJAAcD+QP5AAYABgP6A/oAAAgAAAIADgAQAABMAGwAAGQEpAR0BOwEdASsBHQEpAR0BKwEBNSsBHQE7AQGAAYBAQEBA/wD/AICAAoDAwMDAAkABwEBAwMBAQICAAkDAwMAAAAAAAQAAAIADgAQAADcAAAE1KQEdATsBHQErAT0BKwEdASsBHQE7AR0BOwE9ATsBHQErAR0BKQE9ASsBPQErAT0BOwE9ATsBAQABAAEAQECAgICAQEBAQICAgIBAQP8A/wBAQEBAQEBAQAPAQEBAQEBAQEBAwMBAQEBAQEBAQEBAQEDAwEBAAAAAAAEAgACAA4AEAAAPAAATNSkBHQErARkBKwEZASsBgAGAAYCAgICAgIADwEBAQP6A/oABgAGAAAAAAQAAAIADgAQAACcAABE1OwEdATsBPQE7ARkBKwEdASkBPQErAT0BOwEdATsBPQEpAT0BKwGAgMDAgIBAQP7A/sBAQICAwMD/AP8AQEADQMDAwMDA/oD+gEBAQEBAQEBAgIBAQAAAAAADAAAAgAOABAAAJwAvADcAAAE1OwEdATsBHQE7AR0BKwEdASsBHQErAT0BKwE9ASsBPQE7AT0BOwERNSsBHQE7ASU1KwEdATsBAYBAQICAQEBAQICAQECAgEBAQECAgEBAQEABAEBAQEADwEBAQEBAwMBAQEBAQEBAQMDAQED+wMDAwMDAwMAAAQAAAIADgAQAAEcAABE1OwEdATsBHQE7AT0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBPQErAR0BKwEdASsBPQE7AT0BOwE9ASsBPQErAYCAQEBAQEBAgIBAQEBAQEBAQICAQEBAQEBAgIBAQEBAQEBAQAOAgEBAQEBAQEBAgIBAQEBAQECAgEBAQEBAQEBAgIBAQEBAQEAAAAEAAAAAA4AEAAAXAAAZATsBGQE7ARkBOwEZATsBHQErAT0BKQGAgICAgIBAQICA/sD+wAJAAcD+gP6AAYABgP6A/oCAgEBAAAEAAACAA4AEAAAXAAARNTsBHQE7AT0BOwEZASsBPQEpAT0BKwGAgMDAgICAgP8A/wBAQANAwMDAwMD+QP5AwMBAQAAAAQAAAIADgAQAABcAABkBOwEZATsBGQE7ARkBOwEZATsBGQEpAYCAQEBAQEBAgID+QP5AAkABwP6A/oABgAGA/oD+gAGAAYD+QP5AAAEAAAAABAAEAAAfAAAZATsBGQE7ARkBOwEZATsBGQE7ARkBOwEdASsBPQEpAYCAQEBAQEBAgIBAQICA/oD+gAJAAcD+gP6AAYABgP6A/oABgAGA/oD+gICAQEAAAgAAAIADgAQAABcAHwAAETU7AR0BOwEdATsBHQErAR0BKQEZASsBATUrAR0BOwHAwMDAQEBAQP7A/sBAQAKAgICAgAPAQICAQEDAwEBAAYABgP5AwMDAAAIAAACAA4AEAAAbACMAABkBOwEdATsBHQE7AT0BOwEZASsBPQErAR0BKQEBNSsBHQE7AYCAgIBAQICAgIBAQP8A/wABgEBAQEACQAHAgIBAQMDA/kD+QEBAQEABQMDAwAAAAAACAAAAgAOABAAAEwAbAAAZATsBHQEpAR0BOwEdASsBHQEpAQE1KwEdATsBgIABAAEAQEBAQP6A/oACgMDAwMACQAHAgIBAQMDAQEABQMDAwAAAAAABAAAAgAOABAAAPwAAEzUpAR0BOwEdATsBHQErAR0BKwEdASkBPQErAT0BOwEdATsBPQE7AT0BKQE9ASkBPQErAT0BKwEdASsBPQE7AYABAAEAQEBAQEBAQED/AP8AQECAgICAQED/AP8AAQABAEBAgICAgEBAA8BAQEBAQMDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAIAAACABAAEAAAnAC8AABkBOwEdATsBPQE7AT0BOwEdATsBGQErAR0BKwE9ASsBPQErAR0BKwEBESsBGQE7AYCAQEBAQMDAQEBAQMDAQEBAQICAAwBAQEBAAkABwMDAgIBAQEBA/sD+wEBAQECAgMDAAcABQP7A/sAAAAACAAAAgAOABAAAIwAvAAATNSkBGQErAT0BKwEdASsBHQErAT0BOwE9ATsBPQErAT0BOwEFNSsBHQE7AR0BOwGAAYABgICAQEBAQMDAQEBAQICAQEACAMDAQECAgAPAQP5A/kCAgEBAQEBAQEBAQEDAwMDAgIBAQAACAAAAgAOAAwAAGwAjAAATNSkBHQE7ARkBKQE9ASsBPQE7AT0BKQE9ASkBATUrAR0BOwGAAUABQEBA/oD+gEBAQEABAAEA/wD/AAIAwMDAwALAQEBA/wD/AEBAQEBAQEBA/sBAQEAAAAIAAACAA4AEAAAjAC8AABM1KQEdASkBHQE7AT0BOwEdATsBHQErAR0BKQE9ASsBGQE7AQE1KwEdASsBHQE7AYABgAGA/sD+wEBAwMBAQEBA/sD+wEBAQEACAICAQEDAwAPAQEBAgIBAQEBAwMBAQEBAAUABQP5AwEBAgIAAAAAAAwAAAIADgAMAABcAHwAnAAAZASkBHQE7AR0BKwEdATsBHQErAR0BKQEBNSsBHQE7ARU1KwEdATsBAYABgEBAQEBAQEBA/oD+gAKAwMDAwMDAwMABwAFAQEBAQEBAQEBAQAHAQEBAwEBAQAAAAQCAAIADgAMAAAsAABMRKQEdASkBGQErAYABgAGA/wD/AICAAcABQEBA/wD/AAAAAgAAAAADgAMAABsAIwAAEzUpARkBKwEdASkBPQEpAT0BKQE9ASsBPQE7AQU1KwEdATsBgAGAAYBAQP7A/sABAAEA/wD/AEBAQEACAMDAwMACwED+wP7AQEBAQEBAQECAgICAgIAAAAACAAAAgAOAAwAAGwAjAAATNSkBHQE7AR0BKQEdASkBHQEpAT0BKwE9ATsBBTUrAR0BOwGAAUABQEBA/sD+wAEAAQD+wP7AQEBAQAIAwMDAwALAQEBAgIBAQEBAQEDAwEBAQEAAAQAAAIADgAMAADcAABE1OwEdATsBPQE7AR0BOwE9ATsBHQErAR0BOwEdASsBPQErAR0BKwE9ASsBHQErAT0BOwE9ASsBgIBAQEBAQECAgEBAQECAgEBAQEBAQICAQEBAQAKAgICAgICAgICAgIBAQICAgICAgICAgICAgEBAAAABAAAAgAOAAwAAJwAAETUpAR0BOwEdASsBHQE7AR0BKwEdASkBPQEpAT0BKQE9ASkBPQEpAQGAAYBAQEBAQEBAQP6A/oABQAFA/wD/AAEAAQD+wP7AAsBAQEBAQEBAQEBAQEBAQEBAQEBAAAABAAAAgAOAAwAAJwAAGQE7AR0BOwE9ATsBPQE7AT0BOwEZASsBPQErAR0BKwEdASsBHQErAYCAQEBAQEBAgICAgEBAQEBAQICAAcABQMDAQEBAQEBA/sD+wMDAQEBAQEBAAAAAAgAAAIADgAQAAC8APwAAEzU7AR0BOwE9ATsBHQErAR0BOwEZASsBPQErAR0BKwEdASsBHQErARkBOwE9ASsBBTUrAR0BOwE9ATsBPQE7AYBAQMDAQEBAQICAgIBAQEBAQECAgICAQEACAMDAQEBAQEBAA8BAQEBAQEBAQED+wP7AwMBAQEBAQEABQAFAQEDAQMDAQEBAQAABAAAAgAOAAwAALwAAGQE7AR0BOwE9ATsBPQE7AR0BKwEdASsBHQE7AR0BOwEdASsBPQErAT0BKwEdASsBgICAgEBAgIBAQEBAQEBAQICAQECAgICAAcABQICAQEBAQEBAQEBAQEBAQEBAQEBAgIAAAQAAAIADgAMAABsAAAE1KQEZASsBGQErAR0BKwEdASsBPQE7AT0BOwEBAAFAAUCAgICAQECAgEBAQEACwED+wP7AAQABAICAgICAgICAAAABAAAAgAOAAwAAJwAAGQE7AR0BOwEdATsBPQE7AT0BOwEZASsBPQErAR0BKwE9ASsBHQErAYCAQEBAQEBAgICAgEBAQEBAQICAAcABQEBAQEBAQEBA/sD+wMDAgICAgMDAAAAAAQAAAIADgAMAABcAABkBOwEdATsBPQE7ARkBKwE9ASsBHQErAYCAwMCAgICAwMCAgAHAAUCAgICA/sD+wICAgIAAAAACAAAAgAOAAwAAFwAfAAATNSkBHQE7AR0BKwEdASkBPQErAT0BOwEFNSsBHQE7AYABQAFAQEBAQP7A/sBAQEBAAgDAwMDAAsBAQEDAwEBAQEDAwMDAwMAAAQAAAIADgAMAAA8AABkBKQEZASsBGQErARkBKwEBwAHAgIDAwICAAcABQP7A/sABAAEA/wD/AAACAAAAAAOAAwAAEwAbAAAZASkBHQE7AR0BKwEdASkBHQErAQE1KwEdATsBAYABgEBAQED/AP8AgIACgMDAwMABgAGAQECAgEBAgIACAICAgAAAAAABAAAAgAOAAwAAFwAAEzUpAR0BKQEdASkBHQEpAT0BKwE9ATsBgAGAAYD+wP7AAUABQP6A/oBAQEBAAsBAQEDAwEBAQEDAwAABAIAAgAOAAwAADwAAEzUpAR0BKwEZASsBGQErAYABgAGAgICAgICAAsBAQED/AP8AAQABAAAAAAEAAAAAA4ADAAAfAAARNTsBHQE7AT0BOwEZASsBHQEpAT0BKQE9ASkBPQErAYCAwMCAgEBA/sD+wAEAAQD/AP8AQEACQMDAwMDA/sD+wEBAQEBAQEBAAAADAAAAAAOAA4AAJwAvADcAAAE1OwEdATsBHQE7AR0BKwEdASsBHQErAT0BKwE9ASsBPQE7AT0BOwERNSsBHQE7ASU1KwEdATsBAYBAQICAQEBAQICAQECAgEBAQECAgEBAQEABAEBAQEADQEBAQEBAwMBAQEBAQEBAQMDAQED+wMDAwMDAwMAAAQAAAIADgAMAACcAABE1OwEdATsBPQE7AR0BKwEdATsBHQErAT0BKwEdASsBPQE7AT0BKwGAgMDAgICAgICAgIDAwICAgICAgAKAgEBAQECAgEBAgIBAQEBAgIBAQAAAAQAAAAADgAMAABcAABkBOwEZATsBGQE7ARkBOwEdASsBPQEpAYCAgICAgEBAgID+wP7AAcABQP8A/wABAAEA/wD/AICAQEAAAQAAAIADgAMAABcAABE1OwEdATsBPQE7ARkBKwE9ASkBPQErAYCAwMCAgICA/wD/AEBAAoCAgICAgP7A/sCAgEBAAAABAAAAgAOAAwAAFwAAGQE7ARkBOwEZATsBGQE7ARkBOwEZASkBgIBAQEBAQECAgP5A/kABwAFA/wD/AAEAAQD/AP8AAQABAP7A/sAAAQAAAAAEAAMAAB8AABkBOwEZATsBGQE7ARkBOwEZATsBGQE7AR0BKwE9ASkBgIBAQEBAQECAgEBAgID+gP6AAcABQP8A/wABAAEA/wD/AAEAAQD/AP8AgIBAQAACAAAAgAOAAwAAFwAfAAARNTsBHQE7AR0BOwEdASsBHQEpARkBKwEBNSsBHQE7AcDAwMBAQEBA/sD+wEBAAoCAgICAAsBAgIBAQEBAQEABAAEA/sBAQEAAAgAAAIADgAMAABsAIwAAGQE7AR0BOwEdATsBPQE7ARkBKwE9ASsBHQEpASU1KwEdATsBgICAgEBAgICAgEBA/wD/AAGAQEBAQAHAAUCAgEBAwMD+wP7AQEBAQMBAQEAAAgAAAIADgAMAABMAGwAAGQE7AR0BKQEdATsBHQErAR0BKQElNSsBHQE7AYCAAQABAEBAQED+gP6AAoDAwMDAAcABQICAQEBAQEBAwEBAQAABAAAAgAOAAwAALwAAEzUpAR0BOwEdASsBHQEpAT0BKwE9ATsBHQE7AT0BKwE9ATsBPQErAR0BKwE9ATsBgAFAAUBAQEBA/sD+wEBAgIDAwICAgIDAwICAQEACwEBAQMDAQEBAQEBAQEBAQEBAQEBAQEBAAAIAAACABAADAAAnAC8AABkBOwEdATsBPQE7AT0BOwEdATsBHQErAR0BKwE9ASsBPQErAR0BKwEBNSsBHQE7AYCAQEBAQMDAQEBAQMDAQEBAQICAAwBAQEBAAcABQICAQEBAQEBAwMBAQEBAQECAgAFAwMDAAAAAAAIAAACAA4ADAAAbACMAABM1KQEZASsBPQErAR0BKwE9ATsBPQErAT0BOwEFNSsBHQE7AYABgAGAgIDAwICAQEBAQEBAAgDAwMDAAsBA/sD+wICAgICAgEBAQEBAQEBAAAIAAACAA4AEAAArADMAAAE1OwEdATsBHQE7AR0BOwEdASkBHQEpAR0BKQE9ASsBPQE7AT0BOwE9ASsBATUrAR0BOwEBAEBAQECAgEBA/sD+wAEAAQD+wP7AQEBAQICAQEABgMDAwMADwEBAQEBAQECAgEBAQEBAQMDAQEBAQP7AQEBAAAQAAACAA4AEAAAbACMAKwAzAAATNSkBHQE7AR0BKQEdASkBHQEpAT0BKwE9ATsBBTUrAR0BOwEDNTsBHQErASU1OwEdASsBgAFAAUBAQP7A/sABAAEA/sD+wEBAQEACAMDAwMCAgICAgP6AgICAgALAQEBAgIBAQEBAQEDAwEBAQEABwEBAQEBAQEAAAQAAAAADgAQAAC8AABM1OwEdATsBHQErAR0BOwEdATsBHQErAR0BKwE9ATsBPQErAR0BKwEZASsBPQE7AYCAgICAgIDAwEBAQECAgEBAgICAgEBAQEADwEBAQEBAQEBAQMDAQEBAQMDAwMABQAFAQEAAAAABAIAAgAOABAAAGwAAATU7AR0BKwEdATsBHQEpARkBKwEZATsBPQE7AQIAQEBAQMDA/wD/AICAgIBAQAPAQEBAQEBAQP8A/wABQAFAQEAAAAEAAACAA4ADAAAvAAATNSkBHQE7AR0BKwE9ASsBHQE7AR0BKwEdATsBPQE7AR0BKwEdASkBPQErAT0BOwGAAUABQEBAgIDAwICAgIDAwICAQED+wP7AQEBAQALAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMAAAQAAAIADgAMAACcAABM1KQEdASkBHQEpAR0BOwEdASsBHQEpAT0BKQE9ASkBPQErAT0BOwGAAUABQP8A/wABAAEAQEBAQP6A/oABQAFA/wD/AEBAQEACwEBAQEBAQEBAQEBAQEBAQEBAQEAAAgCAAIADgAQAABMAGwAAATU7ARkBOwEdASkBPQE7AT0BKwETNTsBHQErAQEAwMCAgP6A/oCAgEBAgICAgIACwED/AP8AQEBAQMDAAUBAQEAAAAAAAwCAAIADgAQAABMAGwAjAAABNTsBGQE7AR0BKQE9ATsBPQErAQE1OwEdASsBJTU7AR0BKwEBAMDAgID+gP6AgIBAQAEAgICAgP6AgICAgALAQP8A/wBAQEBAwMABQEBAQEBAQEAAAAACAIAAAAMABAAAEwAbAAABNTsBGQErAR0BKQE9ATsBGQErARM1OwEdASsBAYDAwEBA/wD/AMDAQECAgICAgALAQP7A/sBAQEBAAQABAAFAQEBAAAACAAAAgAOAAwAAIwArAAABNTsBHQE7AR0BOwEdASsBHQErAT0BKwEdASsBPQE7AT0BOwEBNSsBHQE7AQEAgICAgEBAQEDAwEBAgIBAQEBAAYBAQEBAAsBAQEBAQICAQEDAwMDAwMBAQP8AgICAAAAAAgAAAIADgAMAACMAKwAAGQE7AR0BOwE9ATsBHQE7AR0BOwEdASsBHQErAT0BKwEdASsBATUrAR0BOwGAgEBAQECAgEBAQEDAwEBAgIACgEBAQEABwAFAgICAgEBAQECAgEBAgICAgAEAgICAAAAAAAEAAACAA4AEAAAnAAATNTsBHQE7AR0BKwEdATsBHQE7AR0BKwE9ASsBHQErARkBKwE9ATsBgICAgICAgMDAQECAgICAgIBAQEBAA8BAQEBAQEBAQEDAwMDAwMABQAFAQEAAAAACAAAAgAOABAAALwA/AAAZATsBHQE7AT0BOwE9ATsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBPQErAR0BKwEBNTsBHQErAR0BKwE9ATsBgICAgEBAgIBAQEBAQEBAQICAQECAgICAAgBAQEBAQEBAQAHAAUCAgEBAQEBAQEBAQEBAQEBAQEBAQICAA0BAQEBAQEBAAAAAAAIAAACAA4AEAAAnADcAABkBOwEdATsBPQE7AT0BOwE9ATsBGQErAT0BKwEdASsBHQErAR0BKwEBNTsBHQE7AR0BKwE9ASsBgIBAQEBAQECAgICAQEBAQEBAgIABAEBAQEBAQEBAAcABQMDAQEBAQEBA/sD+wMDAQEBAQEBAA0BAQEBAQEBAAAACAAAAAAOABAAALwA3AAATNTsBHQE7AT0BOwEdASsBHQE7ARkBKwEdASkBPQEpAT0BKQE9ASsBPQE7AT0BKwEBNSsBHQE7AYBAQMDAQEBAQICAQED+wP7AAQABAP8A/wBAQICAQEACAMDAwMADwEBAQEBAQEBAQP7A/sBAQEBAQEBAQMDAQED+wMDAwAAAAAABAAAAAAOAAwAAFwAAGQE7ARkBOwEZATsBGQErAR0BKwE9ASsBgIDAwICAwMBAQMDAAcABQP8A/wABAAEA/sD+wEBAQEAAAAABAAACAAOAAoAABwAAETUpAR0BKQEBwAHA/kD+QAJAQEBAAAABAAACAAQAAoAABwAAETUpAR0BKQECAAIA/gD+AAJAQEBAAAABAAACAAOAAoAABwAAETUpAR0BKQEBwAHA/kD+QAJAQEBAAAABAQACgAKABAAADwAAATU7AR0BKwEdASsBPQE7AQGAgIBAQICAQEADwEBAQICAgIAAAAAAAQEAAoACgAQAAA8AAAE1OwEdASsBHQErAT0BOwEBgICAQECAgEBAA4CAgIBAQEBAAAAAAAEBAACAAoACAAAPAAABNTsBHQErAR0BKwE9ATsBAYCAgEBAgIBAQAGAgICAQEBAQAAAAAABAIACgAOABAAAHwAAATU7AR0BOwE9ATsBHQErAR0BKwE9ASsBHQErAT0BOwEBAICAQECAgEBAgIBAQICAQEADwEBAQEBAQECAgICAgICAgAAAAAABAIACgAOABAAAHwAAATU7AR0BOwE9ATsBHQErAR0BKwE9ASsBHQErAT0BOwEBAICAQECAgEBAgIBAQICAQEADgICAgICAgIBAQEBAQEBAQAAAAAABAAAAgAMAAgAAHwAAEzU7AR0BOwE9ATsBHQErAR0BKwE9ASsBHQErAT0BOwGAgIBAQICAQECAgEBAgIBAQAGAgICAgICAgEBAQEBAQEBAAAEAgACAA4AEAAAXAAABNTsBHQE7AR0BKwEZASsBGQErAT0BOwEBgICAgICAgICAgICAgAOAgICAQED/AP8AAQABAEBAAAAAAAEAgACAA4AEAAAnAAABNTsBHQE7AR0BKwEdATsBHQErAR0BKwE9ASsBPQE7AT0BKwE9ATsBAYCAgICAgICAgICAgICAgICAgICAgAOAgICAQEBAQEBAgICAgEBAQEBAQAAAAAABAQABAAMAAwAAFwAAATU7AR0BOwEdASsBHQErAT0BKwE9ATsBAYCAgEBAQECAgEBAQEACwEBAQICAQEBAQICAAAAAAAMAAACAA4ABgAAHAA8AFwAAATU7AR0BKwElNTsBHQErASU1OwEdASsBAwBAQEBA/oBAQEBA/oBAQEBAAQCAgICAgICAgICAgAAAAAAFAAAAgAQABAAAPwBPAFcAZwBvAAABNTsBHQErAR0BKwEdASsBHQEpAR0BKwEdASkBPQErAR0BKwEdASsBPQE7AT0BOwE9ATsBPQE7AT0BOwE9ATsBATUrAR0BOwEdATsBPQErAQU1KwEdATsBATU7AR0BKwEdASsBPQE7ARc1KwEdATsBAwBAQEBAQEBAQAEAAQBAQP8A/wBAQEBAQEBAQEBAQEBAQEBAQED/AEBAQEBAQEBAAYBAQEBA/QCAgEBAgIBAQIBAQEBAA8BAQEBAQEBAQECAgEBAgIBAQEBAQEBAQEBAQEBAQEBA/kBAQEBAQEBAQEBAQALAQICAQECAgEBAQEAAAAABAQABAAMAA4AAJwAAATU7AR0BKwEdASsBHQE7AR0BOwEdASsBPQErAT0BKwE9ATsBPQE7AQIAgIBAQEBAQEBAQICAQEBAQEBAQEADQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAQCAAQACgAOAACcAABM1OwEdATsBHQE7AR0BKwEdASsBHQErAT0BOwE9ATsBPQErAT0BKwGAgIBAQEBAQEBAQICAQEBAQEBAQEADQEBAQEBAQEBAQEBAQEBAQEBAQEAAAQAAAIADgAQAADcAAAE1OwEdASsBHQErAR0BKwEdASsBHQErAR0BKwEdASsBPQE7AT0BOwE9ATsBPQE7AT0BOwE9ATsBAwBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAwAAAIADgAQAADcAPwBHAAABNTsBHQE7AR0BKwEdASsBHQE7AR0BOwEdASsBHQErAT0BKwE9ASsBPQE7AT0BKwE9ATsBPQE7AQU1KwEdATsBETUrAR0BOwEBgMDAQECAgICAgICAgEBAwMBAQICAQEBAQICAQEABAEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA/kBAQEAAAwAAAAADgAQAACsAMwA7AAATNTsBHQE7AR0BOwE9ATsBHQE7AR0BKwEdASsBHQErAT0BKwEdASsBGQE7AQERKwEZATsBJTUrAR0BOwGAgIBAQEBAQEBAQEBAQEBAQEBAwMBAQAEAgICAgAGAQEBAQAPAQEBAgIBAQEBAwMBAQEBAgIBAQAGAAYD+wAFA/sD+wMDAwMAAAAAEAAAAgAOABAAABwAfACcAOwAAJTU7AR0BKwETNTsBHQE7AR0BKwEdASsBPQErAT0BOwEXNSsBHQE7ASUROwEdATsBGQErARkBKwEZASsBAgDAwMDAgEBAQEBAQEBAQEBAQIBAQEBA/QCAgEBAQEBAQEBAwEBAQANAQEBAwMBAQEBAwMDAwMDAQAHAQED+gP6AAYABgP6A/oAAAAABAIACAAOABAAAGwAAEzUpARkBKwE9ASsBHQErAT0BKwEdASsBPQErAYABgAGAQEBAQEBAQEBAQEBAA8BA/wD/AICAgIDAwMDAwMAAAQAAAIAEAAQAACcAAAE1OwEdASkBHQEpAR0BKwE9ASsBPQErAT0BKwE9ATsBPQE7AT0BOwEBgEBAAQABAP8A/wBAQEBAQEBAQEBAQEBAQAPAQICAwMCAgEBAQEBAQEBAQEBAQAAAAAABAAAAAAOABAAAJwAAATU7AR0BOwEdATsBHQE7AR0BKwEZASsBGQErAT0BOwE9ATsBPQE7AQGAQEBAQEBAQECAgMDAgIBAQEBAQEADwEBAQEBAQEBAQP8A/wABAAEAQEBAQEBAAAAAAAEAAACABAAEAAAnAAABNTsBHQE7AR0BOwEdATsBHQErAR0BKwEdASsBHQErAT0BKQE9ASkBAgBAQEBAQEBAQEBAQEBAQEBA/wD/AAEAAQADgIBAQEBAQEBAQEBAQEBAQICAwMAAAAAAAQAAAAADgAQAACcAAAEROwEZATsBHQErAR0BKwEdASsBHQErAT0BKwE9ASsBPQErAT0BOwEBAMDAgIBAQEBAQEBAQEBAQEBAQICAAwABAP8A/wBAQEBAQEBAQEBAQEBAQEBAAAIAAACAA4AEAAAvADcAABM1KQEdATsBHQE7ARkBKwEdASkBPQErAT0BOwE9ASkBPQErAT0BKwEdASsBPQE7AQE1KwEdATsBgAEAAQBAQEBAQED+wP7AQEBAQAEAAQBAQICAgIBAQAIAwMDAwAPAQEBAQED/AP8AQEBAQICAQEBAQEBAQEBAQP4AgICAAAAAAAIAAACAA4AEAAAXACcAAAE1OwEdATsBHQE7ARkBKQEZATsBPQE7AQU1KwEdASsBGQE7ARkBKwEBAMDAQEBAQP5A/kBAQEBAAQBAQEBAwMBAQAPAQEBAQED+wP7AAUABQEBAQEBAQP8A/wABAAEAAAABAAAAgAOABAAADwAAGQEpARkBKwEZASsBGQErAQHAAcCAgMDAgIACQAHA/kD+QAGAAYD+gP6AAAEAAACAA4AEAAA/AAARNSkBHQErAT0BKwEdATsBHQE7AR0BKwEdASsBHQE7AT0BOwEdASkBPQE7AT0BOwE9ATsBPQErAT0BKwE9ASsBAcABwICAgIBAQICAgIBAQICAgID+QP5AQEBAQEBAQEBAQEBAA8BAgIBAQEBAQEBAQEBAQEBAQICAQEBAQEBAQEBAQEBAAAABAAAAgAOABAAAGwAAAREpAR0BKwEZASsBPQErAT0BKwE9ATsBHQE7AQGAAQABAICAwMBAQEBAgIBAQALAAUBAQP6A/oBAQEBAQEBAQAAAAAMAAAEAA4ADAAAnAC8ANwAAEzU7AR0BOwE9ATsBHQE7AR0BKwEdASsBPQErAR0BKwE9ASsBPQE7AQU1KwEdATsBJTUrAR0BOwGAgIBAQICAQEBAQICAQECAgEBAQEABAICAgIABgICAgIACwEBAQEBAQECAgEBAQEBAQEBAgICAgICAgICAgAABAIAAAAOABAAAJwAAATU7AR0BOwEdASsBPQErARkBKwEdASsBPQErAT0BOwEdATsBGQE7AQIAgIBAQEBAQEBAQICAQEBAQEBAQEADwEBAQEBAQED+gP6AQEBAQEBAQEABgAGAAAAAAAIAAAEAAwADgAAfAD8AABM1OwEdATsBPQE7AR0BKwEdASsBPQErAR0BKwE9ATsBETU7AR0BOwE9ATsBHQErAR0BKwE9ASsBHQErAT0BOwGAgICAgEBAQECAgICAQEBAQICAgIBAQEBAgICAgEBAQEABwEBAQEBAQEBAQEBAQEBAQAHAQEBAQEBAQEBAQEBAQEBAAAABAAAAgAOABAAANwAAATU7AR0BKwEdATsBHQErAR0BOwEdASkBHQErAR0BKwE9ATsBPQErAT0BOwE9ASsBPQEpAT0BOwEDAEBAQEBAQMDAwMD+wP7AQEBAQEBAQEDAwMDAAUABQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAgEAAIADAAQAAAcALwAAJTUpAR0BKQEBNTsBHQErAR0BKwEdATsBHQE7AR0BKwE9ASsBPQErAT0BOwE9ATsBAQABAAEA/wD/AAEAgIBAQEBAQEBAQICAQEBAQEBAQEDAQEBAA0BAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAIBAACAAwAEAAAHAC8AACU1KQEdASkBETU7AR0BOwEdATsBHQErAR0BKwEdASsBPQE7AT0BOwE9ASsBPQErAQEAAQABAP8A/wCAgEBAQEBAQEBAgIBAQEBAQEBAQMBAQEADQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAEAAACAA4AEAAAfAAABNTsBHQE7AR0BOwEdATsBHQEpAT0BOwE9ATsBPQE7AQGAQEBAQEBAQED+QP5AQEBAQEBAA4CAgICAgICAQEBAQICAgIAAAAEAAACAA4AEAAAfAAAZATsBHQE7AR0BOwEdATsBHQErAR0BKwEdASsBHQErAUBAgICAgICAgICAgICAQEACQAHAQEBAQEBAQEBAQEBAQEAAAQAAAIADgAQAAB8AABE1KQEdASsBHQErAR0BKwEdASsBPQErAT0BKwE9ASsBAcABwEBAQEBAQEBAQEBAQEBAA8BAQECAgICAgICAgICAgIAAAAAAAQAAAIADgAQAAB8AAAE1OwEZASsBPQErAT0BKwE9ASsBPQE7AT0BOwE9ATsBAwBAQEBAgICAgICAgICAgICAA8BA/kD+QEBAQEBAQEBAQEBAQAAAAgAAAIADgAQAADcAXwAAATU7AR0BOwEdATsBHQE7AR0BKwEdASsBHQErAR0BKwE9ASsBPQErAT0BKwE9ATsBPQE7AT0BOwEXNSsBHQErAR0BKwEdATsBHQE7AR0BOwE9ATsBPQE7AT0BKwE9ASsBAYBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAABAAAAgAOABAAANwAAATU7AR0BOwEdASsBHQErAR0BOwEdASsBPQErAT0BKwEdASsBHQErAT0BOwE9ASsBPQErAT0BOwEBgEBAwMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMADgICAgEBAQEBAQICAQEBAQEBAQECAgEBAQEBAQAAAAAACAAAAgAOABAAALwBHAAABNTsBHQE7AR0BKwEdASsBHQE7AR0BKwE9ASsBHQErAT0BOwE9ASsBPQErAT0BOwEXNSsBHQErAR0BOwEdATsBPQE7AT0BKwEBgEBAwMBAQEBAQEBAQMDAQEBAQEBAQEDAwIBAQEBAQEBAQEBAQEADgICAgEBAQEBAQICAQEBAQICAQEBAQEBAQEBAQEBAQEBAQEBAAAEAAACAA4AEAAAvAAABNTsBHQE7AR0BOwEdATsBHQErAR0BOwEdASkBPQE7AT0BKwE9ATsBPQE7AT0BOwEBgEBAQEBAQEBAgIBAQP7A/sBAQICAQEBAQEBAA8BAQEBAQEBAgIBAQEBAQEBAQICAQEBAQAAAAgCAAIADgAQAAB8AJwAAATU7AR0BOwEdASsBHQE7AR0BKQE9ATsBPQErAT0BOwEFNSsBHQE7AQGAgICAgICAQED/AP8AQECAgICAAQCAgICAA4CAgICAgICAQEBAQICAgICAgICAAAABAAAAgAOABAAALwAAEzU7AR0BOwE9ATsBHQE7AR0BKwEdASsBHQErAR0BKwE9ASsBPQErAT0BKwE9ATsBgICAQECAgEBAQEBAQEBAQEBAQEBAQEBAQAPAQEBAQEBAQMDAQEBAQEBAQEBAQEBAwMAAAQAAAIADgAQAADcAAAE1OwEdATsBHQE7AR0BOwEdASsBHQErAR0BKwEdASsBPQErAT0BKwE9ASsBPQE7AT0BOwE9ATsBAYBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAA8BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAQAAAIADgAQAADcAAAEROwEdATsBHQE7AR0BOwEdASsBHQErAT0BOwE9ASsBPQErARkBKwEdASsBPQErAT0BOwE9ATsBAYBAQEBAQEBAQEBAQEBAQEBAQEBAQICAQEBAQICAAwABAEBAQEBAQEBAQEBAQEBAQED/AP8AQEBAQEBAQEAAAwCAAIADgAQAAAcAFwAfAAATESkBGQEpAQE1KQEdATsBHQErAR0BOwEFNSsBHQE7AYABgAGA/oD+gAKA/wD/AMDAgIDAwP8AQEBAQAJAAcD+QP5AAkDAQEBAQEBAwEBAQAAAAAEAgACAAwABgAALAAABNTsBHQEpAT0BOwEBgMDA/sD+wICAAUBAgIBAQAAAAwAAAIAEAAQAAC8ANwBHAAABNTsBHQE7AR0BOwEdASsBHQErAR0BKwE9ASsBHQErAT0BKwE9ASsBPQE7AT0BOwEXNSsBHQE7AQU1KwEdASsBHQE7AT0BOwECAEBAgIBAQEBAQECAgEBAQEBAQEBAQEDAwIBAQEBAAQBAQEBAQEBAQAPAQEBAQEDAwEBAQEBAQEBAQEBAQMDAQEBAQEBAgICAgEBAQEAAAQAAAIADgAQAACMAAAE1OwEdASsBHQEpARkBKwEZASsBGQErARkBKwE9ATsBPQE7AQEAwMCAgAEAAQCAgICAgIBAQEBAQEADwEBAQEBA/sD+wAEAAQD/AP8AAQABAEBAQEAAAAIAAACAA4AEAAAbACMAAAE1KQEZASsBGQErARkBKwEZASsBPQE7AT0BOwEFNSsBHQE7AQEAAUABQICAgICAgEBAQEBAQAGAgICAgAPAQP5A/kABAAEA/wD/AAEAAQBAQEBAQEBAQAAAAAASAN4AAQAAAAAAAABOAJ4AAQAAAAAAAQAOAQsAAQAAAAAAAgAHASoAAQAAAAAAAwAqAYgAAQAAAAAABAAOAdEAAQAAAAAABQANAfwAAQAAAAAABgAMAiQAAQAAAAAADRE1JJ0AAQAAAAAADgAaNgkAAwABBAkAAACcAAAAAwABBAkAAQAcAO0AAwABBAkAAgAOARoAAwABBAkAAwBUATIAAwABBAkABAAcAbMAAwABBAkABQAaAeAAAwABBAkABgAYAgoAAwABBAkADSJqAjEAAwABBAkADgA0NdMAKABjACkAIAAyADAAMQAxACAAQwBvAGQAeQAgACIAQwBvAGQAZQBNAGEAbgAzADgAIgAgAEIAbwBpAHMAYwBsAGEAaQByAC4AIABSAGUAbABlAGEAcwBlAGQAIAB1AG4AZABlAHIAIAB0AGgAZQAgAFMASQBMACAATwBwAGUAbgAgAEYAbwBuAHQAIABMAGkAYwBlAG4AcwBlAC4AAChjKSAyMDExIENvZHkgIkNvZGVNYW4zOCIgQm9pc2NsYWlyLiBSZWxlYXNlZCB1bmRlciB0aGUgU0lMIE9wZW4gRm9udCBMaWNlbnNlLgAAUAByAGUAcwBzACAAUwB0AGEAcgB0ACAAMgBQAABQcmVzcyBTdGFydCAyUAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAARgBvAG4AdABGAG8AcgBnAGUAIAAyAC4AMAAgADoAIABQAHIAZQBzAHMAIABTAHQAYQByAHQAIAAyAFAAIAA6ACAAMQAyAC0ANgAtADIAMAAxADEAAEZvbnRGb3JnZSAyLjAgOiBQcmVzcyBTdGFydCAyUCA6IDEyLTYtMjAxMQAAUAByAGUAcwBzACAAUwB0AGEAcgB0ACAAMgBQAABQcmVzcyBTdGFydCAyUAAAVgBlAHIAcwBpAG8AbgAgADIALgAxADQAIAAAVmVyc2lvbiAyLjE0IAAAUAByAGUAcwBzAFMAdABhAHIAdAAyAFAAAFByZXNzU3RhcnQyUAAAQwBvAHAAeQByAGkAZwBoAHQAIAAoAGMAKQAgADIAMAAxADEALAAgAEMAbwBkAHkAIAAiAEMAbwBkAGUATQBhAG4AMwA4ACIAIABCAG8AaQBzAGMAbABhAGkAcgAgACgAYwBvAGQAeQBAAHoAbwBuAGUAMwA4AC4AbgBlAHQAKQAsAAoAdwBpAHQAaAAgAFIAZQBzAGUAcgB2AGUAZAAgAEYAbwBuAHQAIABOAGEAbQBlACAAUAByAGUAcwBzACAAUwB0AGEAcgB0AC4ACgAKAFQAaABpAHMAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAaQBzACAAbABpAGMAZQBuAHMAZQBkACAAdQBuAGQAZQByACAAdABoAGUAIABTAEkATAAgAE8AcABlAG4AIABGAG8AbgB0ACAATABpAGMAZQBuAHMAZQAsACAAVgBlAHIAcwBpAG8AbgAgADEALgAxAC4ACgBUAGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGkAcwAgAGMAbwBwAGkAZQBkACAAYgBlAGwAbwB3ACwAIABhAG4AZAAgAGkAcwAgAGEAbABzAG8AIABhAHYAYQBpAGwAYQBiAGwAZQAgAHcAaQB0AGgAIABhACAARgBBAFEAIABhAHQAOgAKAGgAdAB0AHAAOgAvAC8AcwBjAHIAaQBwAHQAcwAuAHMAaQBsAC4AbwByAGcALwBPAEYATAAKAAoACgAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ACgBTAEkATAAgAE8AUABFAE4AIABGAE8ATgBUACAATABJAEMARQBOAFMARQAgAFYAZQByAHMAaQBvAG4AIAAxAC4AMQAgAC0AIAAyADYAIABGAGUAYgByAHUAYQByAHkAIAAyADAAMAA3AAoALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAC0ALQAtAAoACgBQAFIARQBBAE0AQgBMAEUACgBUAGgAZQAgAGcAbwBhAGwAcwAgAG8AZgAgAHQAaABlACAATwBwAGUAbgAgAEYAbwBuAHQAIABMAGkAYwBlAG4AcwBlACAAKABPAEYATAApACAAYQByAGUAIAB0AG8AIABzAHQAaQBtAHUAbABhAHQAZQAgAHcAbwByAGwAZAB3AGkAZABlAAoAZABlAHYAZQBsAG8AcABtAGUAbgB0ACAAbwBmACAAYwBvAGwAbABhAGIAbwByAGEAdABpAHYAZQAgAGYAbwBuAHQAIABwAHIAbwBqAGUAYwB0AHMALAAgAHQAbwAgAHMAdQBwAHAAbwByAHQAIAB0AGgAZQAgAGYAbwBuAHQAIABjAHIAZQBhAHQAaQBvAG4ACgBlAGYAZgBvAHIAdABzACAAbwBmACAAYQBjAGEAZABlAG0AaQBjACAAYQBuAGQAIABsAGkAbgBnAHUAaQBzAHQAaQBjACAAYwBvAG0AbQB1AG4AaQB0AGkAZQBzACwAIABhAG4AZAAgAHQAbwAgAHAAcgBvAHYAaQBkAGUAIABhACAAZgByAGUAZQAgAGEAbgBkAAoAbwBwAGUAbgAgAGYAcgBhAG0AZQB3AG8AcgBrACAAaQBuACAAdwBoAGkAYwBoACAAZgBvAG4AdABzACAAbQBhAHkAIABiAGUAIABzAGgAYQByAGUAZAAgAGEAbgBkACAAaQBtAHAAcgBvAHYAZQBkACAAaQBuACAAcABhAHIAdABuAGUAcgBzAGgAaQBwAAoAdwBpAHQAaAAgAG8AdABoAGUAcgBzAC4ACgAKAFQAaABlACAATwBGAEwAIABhAGwAbABvAHcAcwAgAHQAaABlACAAbABpAGMAZQBuAHMAZQBkACAAZgBvAG4AdABzACAAdABvACAAYgBlACAAdQBzAGUAZAAsACAAcwB0AHUAZABpAGUAZAAsACAAbQBvAGQAaQBmAGkAZQBkACAAYQBuAGQACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAZgByAGUAZQBsAHkAIABhAHMAIABsAG8AbgBnACAAYQBzACAAdABoAGUAeQAgAGEAcgBlACAAbgBvAHQAIABzAG8AbABkACAAYgB5ACAAdABoAGUAbQBzAGUAbAB2AGUAcwAuACAAVABoAGUACgBmAG8AbgB0AHMALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAYQBuAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdwBvAHIAawBzACwAIABjAGEAbgAgAGIAZQAgAGIAdQBuAGQAbABlAGQALAAgAGUAbQBiAGUAZABkAGUAZAAsACAACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYQBuAGQALwBvAHIAIABzAG8AbABkACAAdwBpAHQAaAAgAGEAbgB5ACAAcwBvAGYAdAB3AGEAcgBlACAAcAByAG8AdgBpAGQAZQBkACAAdABoAGEAdAAgAGEAbgB5ACAAcgBlAHMAZQByAHYAZQBkAAoAbgBhAG0AZQBzACAAYQByAGUAIABuAG8AdAAgAHUAcwBlAGQAIABiAHkAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdwBvAHIAawBzAC4AIABUAGgAZQAgAGYAbwBuAHQAcwAgAGEAbgBkACAAZABlAHIAaQB2AGEAdABpAHYAZQBzACwACgBoAG8AdwBlAHYAZQByACwAIABjAGEAbgBuAG8AdAAgAGIAZQAgAHIAZQBsAGUAYQBzAGUAZAAgAHUAbgBkAGUAcgAgAGEAbgB5ACAAbwB0AGgAZQByACAAdAB5AHAAZQAgAG8AZgAgAGwAaQBjAGUAbgBzAGUALgAgAFQAaABlAAoAcgBlAHEAdQBpAHIAZQBtAGUAbgB0ACAAZgBvAHIAIABmAG8AbgB0AHMAIAB0AG8AIAByAGUAbQBhAGkAbgAgAHUAbgBkAGUAcgAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlACAAZABvAGUAcwAgAG4AbwB0ACAAYQBwAHAAbAB5AAoAdABvACAAYQBuAHkAIABkAG8AYwB1AG0AZQBuAHQAIABjAHIAZQBhAHQAZQBkACAAdQBzAGkAbgBnACAAdABoAGUAIABmAG8AbgB0AHMAIABvAHIAIAB0AGgAZQBpAHIAIABkAGUAcgBpAHYAYQB0AGkAdgBlAHMALgAKAAoARABFAEYASQBOAEkAVABJAE8ATgBTAAoAIgBGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACIAIAByAGUAZgBlAHIAcwAgAHQAbwAgAHQAaABlACAAcwBlAHQAIABvAGYAIABmAGkAbABlAHMAIAByAGUAbABlAGEAcwBlAGQAIABiAHkAIAB0AGgAZQAgAEMAbwBwAHkAcgBpAGcAaAB0AAoASABvAGwAZABlAHIAKABzACkAIAB1AG4AZABlAHIAIAB0AGgAaQBzACAAbABpAGMAZQBuAHMAZQAgAGEAbgBkACAAYwBsAGUAYQByAGwAeQAgAG0AYQByAGsAZQBkACAAYQBzACAAcwB1AGMAaAAuACAAVABoAGkAcwAgAG0AYQB5AAoAaQBuAGMAbAB1AGQAZQAgAHMAbwB1AHIAYwBlACAAZgBpAGwAZQBzACwAIABiAHUAaQBsAGQAIABzAGMAcgBpAHAAdABzACAAYQBuAGQAIABkAG8AYwB1AG0AZQBuAHQAYQB0AGkAbwBuAC4ACgAKACIAUgBlAHMAZQByAHYAZQBkACAARgBvAG4AdAAgAE4AYQBtAGUAIgAgAHIAZQBmAGUAcgBzACAAdABvACAAYQBuAHkAIABuAGEAbQBlAHMAIABzAHAAZQBjAGkAZgBpAGUAZAAgAGEAcwAgAHMAdQBjAGgAIABhAGYAdABlAHIAIAB0AGgAZQAKAGMAbwBwAHkAcgBpAGcAaAB0ACAAcwB0AGEAdABlAG0AZQBuAHQAKABzACkALgAKAAoAIgBPAHIAaQBnAGkAbgBhAGwAIABWAGUAcgBzAGkAbwBuACIAIAByAGUAZgBlAHIAcwAgAHQAbwAgAHQAaABlACAAYwBvAGwAbABlAGMAdABpAG8AbgAgAG8AZgAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUAIABjAG8AbQBwAG8AbgBlAG4AdABzACAAYQBzAAoAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYgB5ACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApAC4ACgAKACIATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAiACAAcgBlAGYAZQByAHMAIAB0AG8AIABhAG4AeQAgAGQAZQByAGkAdgBhAHQAaQB2AGUAIABtAGEAZABlACAAYgB5ACAAYQBkAGQAaQBuAGcAIAB0AG8ALAAgAGQAZQBsAGUAdABpAG4AZwAsAAoAbwByACAAcwB1AGIAcwB0AGkAdAB1AHQAaQBuAGcAIAAtAC0AIABpAG4AIABwAGEAcgB0ACAAbwByACAAaQBuACAAdwBoAG8AbABlACAALQAtACAAYQBuAHkAIABvAGYAIAB0AGgAZQAgAGMAbwBtAHAAbwBuAGUAbgB0AHMAIABvAGYAIAB0AGgAZQAKAE8AcgBpAGcAaQBuAGEAbAAgAFYAZQByAHMAaQBvAG4ALAAgAGIAeQAgAGMAaABhAG4AZwBpAG4AZwAgAGYAbwByAG0AYQB0AHMAIABvAHIAIABiAHkAIABwAG8AcgB0AGkAbgBnACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAdABvACAAYQAKAG4AZQB3ACAAZQBuAHYAaQByAG8AbgBtAGUAbgB0AC4ACgAKACIAQQB1AHQAaABvAHIAIgAgAHIAZQBmAGUAcgBzACAAdABvACAAYQBuAHkAIABkAGUAcwBpAGcAbgBlAHIALAAgAGUAbgBnAGkAbgBlAGUAcgAsACAAcAByAG8AZwByAGEAbQBtAGUAcgAsACAAdABlAGMAaABuAGkAYwBhAGwACgB3AHIAaQB0AGUAcgAgAG8AcgAgAG8AdABoAGUAcgAgAHAAZQByAHMAbwBuACAAdwBoAG8AIABjAG8AbgB0AHIAaQBiAHUAdABlAGQAIAB0AG8AIAB0AGgAZQAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUALgAKAAoAUABFAFIATQBJAFMAUwBJAE8ATgAgACYAIABDAE8ATgBEAEkAVABJAE8ATgBTAAoAUABlAHIAbQBpAHMAcwBpAG8AbgAgAGkAcwAgAGgAZQByAGUAYgB5ACAAZwByAGEAbgB0AGUAZAAsACAAZgByAGUAZQAgAG8AZgAgAGMAaABhAHIAZwBlACwAIAB0AG8AIABhAG4AeQAgAHAAZQByAHMAbwBuACAAbwBiAHQAYQBpAG4AaQBuAGcACgBhACAAYwBvAHAAeQAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAsACAAdABvACAAdQBzAGUALAAgAHMAdAB1AGQAeQAsACAAYwBvAHAAeQAsACAAbQBlAHIAZwBlACwAIABlAG0AYgBlAGQALAAgAG0AbwBkAGkAZgB5ACwACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQAsACAAYQBuAGQAIABzAGUAbABsACAAbQBvAGQAaQBmAGkAZQBkACAAYQBuAGQAIAB1AG4AbQBvAGQAaQBmAGkAZQBkACAAYwBvAHAAaQBlAHMAIABvAGYAIAB0AGgAZQAgAEYAbwBuAHQACgBTAG8AZgB0AHcAYQByAGUALAAgAHMAdQBiAGoAZQBjAHQAIAB0AG8AIAB0AGgAZQAgAGYAbwBsAGwAbwB3AGkAbgBnACAAYwBvAG4AZABpAHQAaQBvAG4AcwA6AAoACgAxACkAIABOAGUAaQB0AGgAZQByACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbgBvAHIAIABhAG4AeQAgAG8AZgAgAGkAdABzACAAaQBuAGQAaQB2AGkAZAB1AGEAbAAgAGMAbwBtAHAAbwBuAGUAbgB0AHMALAAKAGkAbgAgAE8AcgBpAGcAaQBuAGEAbAAgAG8AcgAgAE0AbwBkAGkAZgBpAGUAZAAgAFYAZQByAHMAaQBvAG4AcwAsACAAbQBhAHkAIABiAGUAIABzAG8AbABkACAAYgB5ACAAaQB0AHMAZQBsAGYALgAKAAoAMgApACAATwByAGkAZwBpAG4AYQBsACAAbwByACAATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgBzACAAbwBmACAAdABoAGUAIABGAG8AbgB0ACAAUwBvAGYAdAB3AGEAcgBlACAAbQBhAHkAIABiAGUAIABiAHUAbgBkAGwAZQBkACwACgByAGUAZABpAHMAdAByAGkAYgB1AHQAZQBkACAAYQBuAGQALwBvAHIAIABzAG8AbABkACAAdwBpAHQAaAAgAGEAbgB5ACAAcwBvAGYAdAB3AGEAcgBlACwAIABwAHIAbwB2AGkAZABlAGQAIAB0AGgAYQB0ACAAZQBhAGMAaAAgAGMAbwBwAHkACgBjAG8AbgB0AGEAaQBuAHMAIAB0AGgAZQAgAGEAYgBvAHYAZQAgAGMAbwBwAHkAcgBpAGcAaAB0ACAAbgBvAHQAaQBjAGUAIABhAG4AZAAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlAC4AIABUAGgAZQBzAGUAIABjAGEAbgAgAGIAZQAKAGkAbgBjAGwAdQBkAGUAZAAgAGUAaQB0AGgAZQByACAAYQBzACAAcwB0AGEAbgBkAC0AYQBsAG8AbgBlACAAdABlAHgAdAAgAGYAaQBsAGUAcwAsACAAaAB1AG0AYQBuAC0AcgBlAGEAZABhAGIAbABlACAAaABlAGEAZABlAHIAcwAgAG8AcgAKAGkAbgAgAHQAaABlACAAYQBwAHAAcgBvAHAAcgBpAGEAdABlACAAbQBhAGMAaABpAG4AZQAtAHIAZQBhAGQAYQBiAGwAZQAgAG0AZQB0AGEAZABhAHQAYQAgAGYAaQBlAGwAZABzACAAdwBpAHQAaABpAG4AIAB0AGUAeAB0ACAAbwByAAoAYgBpAG4AYQByAHkAIABmAGkAbABlAHMAIABhAHMAIABsAG8AbgBnACAAYQBzACAAdABoAG8AcwBlACAAZgBpAGUAbABkAHMAIABjAGEAbgAgAGIAZQAgAGUAYQBzAGkAbAB5ACAAdgBpAGUAdwBlAGQAIABiAHkAIAB0AGgAZQAgAHUAcwBlAHIALgAKAAoAMwApACAATgBvACAATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAgAG0AYQB5ACAAdQBzAGUAIAB0AGgAZQAgAFIAZQBzAGUAcgB2AGUAZAAgAEYAbwBuAHQACgBOAGEAbQBlACgAcwApACAAdQBuAGwAZQBzAHMAIABlAHgAcABsAGkAYwBpAHQAIAB3AHIAaQB0AHQAZQBuACAAcABlAHIAbQBpAHMAcwBpAG8AbgAgAGkAcwAgAGcAcgBhAG4AdABlAGQAIABiAHkAIAB0AGgAZQAgAGMAbwByAHIAZQBzAHAAbwBuAGQAaQBuAGcACgBDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByAC4AIABUAGgAaQBzACAAcgBlAHMAdAByAGkAYwB0AGkAbwBuACAAbwBuAGwAeQAgAGEAcABwAGwAaQBlAHMAIAB0AG8AIAB0AGgAZQAgAHAAcgBpAG0AYQByAHkAIABmAG8AbgB0ACAAbgBhAG0AZQAgAGEAcwAKAHAAcgBlAHMAZQBuAHQAZQBkACAAdABvACAAdABoAGUAIAB1AHMAZQByAHMALgAKAAoANAApACAAVABoAGUAIABuAGEAbQBlACgAcwApACAAbwBmACAAdABoAGUAIABDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApACAAbwByACAAdABoAGUAIABBAHUAdABoAG8AcgAoAHMAKQAgAG8AZgAgAHQAaABlACAARgBvAG4AdAAKAFMAbwBmAHQAdwBhAHIAZQAgAHMAaABhAGwAbAAgAG4AbwB0ACAAYgBlACAAdQBzAGUAZAAgAHQAbwAgAHAAcgBvAG0AbwB0AGUALAAgAGUAbgBkAG8AcgBzAGUAIABvAHIAIABhAGQAdgBlAHIAdABpAHMAZQAgAGEAbgB5AAoATQBvAGQAaQBmAGkAZQBkACAAVgBlAHIAcwBpAG8AbgAsACAAZQB4AGMAZQBwAHQAIAB0AG8AIABhAGMAawBuAG8AdwBsAGUAZABnAGUAIAB0AGgAZQAgAGMAbwBuAHQAcgBpAGIAdQB0AGkAbwBuACgAcwApACAAbwBmACAAdABoAGUACgBDAG8AcAB5AHIAaQBnAGgAdAAgAEgAbwBsAGQAZQByACgAcwApACAAYQBuAGQAIAB0AGgAZQAgAEEAdQB0AGgAbwByACgAcwApACAAbwByACAAdwBpAHQAaAAgAHQAaABlAGkAcgAgAGUAeABwAGwAaQBjAGkAdAAgAHcAcgBpAHQAdABlAG4ACgBwAGUAcgBtAGkAcwBzAGkAbwBuAC4ACgAKADUAKQAgAFQAaABlACAARgBvAG4AdAAgAFMAbwBmAHQAdwBhAHIAZQAsACAAbQBvAGQAaQBmAGkAZQBkACAAbwByACAAdQBuAG0AbwBkAGkAZgBpAGUAZAAsACAAaQBuACAAcABhAHIAdAAgAG8AcgAgAGkAbgAgAHcAaABvAGwAZQAsAAoAbQB1AHMAdAAgAGIAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAGUAbgB0AGkAcgBlAGwAeQAgAHUAbgBkAGUAcgAgAHQAaABpAHMAIABsAGkAYwBlAG4AcwBlACwAIABhAG4AZAAgAG0AdQBzAHQAIABuAG8AdAAgAGIAZQAKAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAgAHUAbgBkAGUAcgAgAGEAbgB5ACAAbwB0AGgAZQByACAAbABpAGMAZQBuAHMAZQAuACAAVABoAGUAIAByAGUAcQB1AGkAcgBlAG0AZQBuAHQAIABmAG8AcgAgAGYAbwBuAHQAcwAgAHQAbwAKAHIAZQBtAGEAaQBuACAAdQBuAGQAZQByACAAdABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABkAG8AZQBzACAAbgBvAHQAIABhAHAAcABsAHkAIAB0AG8AIABhAG4AeQAgAGQAbwBjAHUAbQBlAG4AdAAgAGMAcgBlAGEAdABlAGQACgB1AHMAaQBuAGcAIAB0AGgAZQAgAEYAbwBuAHQAIABTAG8AZgB0AHcAYQByAGUALgAKAAoAVABFAFIATQBJAE4AQQBUAEkATwBOAAoAVABoAGkAcwAgAGwAaQBjAGUAbgBzAGUAIABiAGUAYwBvAG0AZQBzACAAbgB1AGwAbAAgAGEAbgBkACAAdgBvAGkAZAAgAGkAZgAgAGEAbgB5ACAAbwBmACAAdABoAGUAIABhAGIAbwB2AGUAIABjAG8AbgBkAGkAdABpAG8AbgBzACAAYQByAGUACgBuAG8AdAAgAG0AZQB0AC4ACgAKAEQASQBTAEMATABBAEkATQBFAFIACgBUAEgARQAgAEYATwBOAFQAIABTAE8ARgBUAFcAQQBSAEUAIABJAFMAIABQAFIATwBWAEkARABFAEQAIAAiAEEAUwAgAEkAUwAiACwAIABXAEkAVABIAE8AVQBUACAAVwBBAFIAUgBBAE4AVABZACAATwBGACAAQQBOAFkAIABLAEkATgBEACwACgBFAFgAUABSAEUAUwBTACAATwBSACAASQBNAFAATABJAEUARAAsACAASQBOAEMATABVAEQASQBOAEcAIABCAFUAVAAgAE4ATwBUACAATABJAE0ASQBUAEUARAAgAFQATwAgAEEATgBZACAAVwBBAFIAUgBBAE4AVABJAEUAUwAgAE8ARgAKAE0ARQBSAEMASABBAE4AVABBAEIASQBMAEkAVABZACwAIABGAEkAVABOAEUAUwBTACAARgBPAFIAIABBACAAUABBAFIAVABJAEMAVQBMAEEAUgAgAFAAVQBSAFAATwBTAEUAIABBAE4ARAAgAE4ATwBOAEkATgBGAFIASQBOAEcARQBNAEUATgBUAAoATwBGACAAQwBPAFAAWQBSAEkARwBIAFQALAAgAFAAQQBUAEUATgBUACwAIABUAFIAQQBEAEUATQBBAFIASwAsACAATwBSACAATwBUAEgARQBSACAAUgBJAEcASABUAC4AIABJAE4AIABOAE8AIABFAFYARQBOAFQAIABTAEgAQQBMAEwAIABUAEgARQAKAEMATwBQAFkAUgBJAEcASABUACAASABPAEwARABFAFIAIABCAEUAIABMAEkAQQBCAEwARQAgAEYATwBSACAAQQBOAFkAIABDAEwAQQBJAE0ALAAgAEQAQQBNAEEARwBFAFMAIABPAFIAIABPAFQASABFAFIAIABMAEkAQQBCAEkATABJAFQAWQAsAAoASQBOAEMATABVAEQASQBOAEcAIABBAE4AWQAgAEcARQBOAEUAUgBBAEwALAAgAFMAUABFAEMASQBBAEwALAAgAEkATgBEAEkAUgBFAEMAVAAsACAASQBOAEMASQBEAEUATgBUAEEATAAsACAATwBSACAAQwBPAE4AUwBFAFEAVQBFAE4AVABJAEEATAAKAEQAQQBNAEEARwBFAFMALAAgAFcASABFAFQASABFAFIAIABJAE4AIABBAE4AIABBAEMAVABJAE8ATgAgAE8ARgAgAEMATwBOAFQAUgBBAEMAVAAsACAAVABPAFIAVAAgAE8AUgAgAE8AVABIAEUAUgBXAEkAUwBFACwAIABBAFIASQBTAEkATgBHAAoARgBSAE8ATQAsACAATwBVAFQAIABPAEYAIABUAEgARQAgAFUAUwBFACAATwBSACAASQBOAEEAQgBJAEwASQBUAFkAIABUAE8AIABVAFMARQAgAFQASABFACAARgBPAE4AVAAgAFMATwBGAFQAVwBBAFIARQAgAE8AUgAgAEYAUgBPAE0ACgBPAFQASABFAFIAIABEAEUAQQBMAEkATgBHAFMAIABJAE4AIABUAEgARQAgAEYATwBOAFQAIABTAE8ARgBUAFcAQQBSAEUALgAAQ29weXJpZ2h0IChjKSAyMDExLCBDb2R5ICJDb2RlTWFuMzgiIEJvaXNjbGFpciAoY29keUB6b25lMzgubmV0KSwKd2l0aCBSZXNlcnZlZCBGb250IE5hbWUgUHJlc3MgU3RhcnQuCgpUaGlzIEZvbnQgU29mdHdhcmUgaXMgbGljZW5zZWQgdW5kZXIgdGhlIFNJTCBPcGVuIEZvbnQgTGljZW5zZSwgVmVyc2lvbiAxLjEuClRoaXMgbGljZW5zZSBpcyBjb3BpZWQgYmVsb3csIGFuZCBpcyBhbHNvIGF2YWlsYWJsZSB3aXRoIGEgRkFRIGF0OgpodHRwOi8vc2NyaXB0cy5zaWwub3JnL09GTAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tClNJTCBPUEVOIEZPTlQgTElDRU5TRSBWZXJzaW9uIDEuMSAtIDI2IEZlYnJ1YXJ5IDIwMDcKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KClBSRUFNQkxFClRoZSBnb2FscyBvZiB0aGUgT3BlbiBGb250IExpY2Vuc2UgKE9GTCkgYXJlIHRvIHN0aW11bGF0ZSB3b3JsZHdpZGUKZGV2ZWxvcG1lbnQgb2YgY29sbGFib3JhdGl2ZSBmb250IHByb2plY3RzLCB0byBzdXBwb3J0IHRoZSBmb250IGNyZWF0aW9uCmVmZm9ydHMgb2YgYWNhZGVtaWMgYW5kIGxpbmd1aXN0aWMgY29tbXVuaXRpZXMsIGFuZCB0byBwcm92aWRlIGEgZnJlZSBhbmQKb3BlbiBmcmFtZXdvcmsgaW4gd2hpY2ggZm9udHMgbWF5IGJlIHNoYXJlZCBhbmQgaW1wcm92ZWQgaW4gcGFydG5lcnNoaXAKd2l0aCBvdGhlcnMuCgpUaGUgT0ZMIGFsbG93cyB0aGUgbGljZW5zZWQgZm9udHMgdG8gYmUgdXNlZCwgc3R1ZGllZCwgbW9kaWZpZWQgYW5kCnJlZGlzdHJpYnV0ZWQgZnJlZWx5IGFzIGxvbmcgYXMgdGhleSBhcmUgbm90IHNvbGQgYnkgdGhlbXNlbHZlcy4gVGhlCmZvbnRzLCBpbmNsdWRpbmcgYW55IGRlcml2YXRpdmUgd29ya3MsIGNhbiBiZSBidW5kbGVkLCBlbWJlZGRlZCwgCnJlZGlzdHJpYnV0ZWQgYW5kL29yIHNvbGQgd2l0aCBhbnkgc29mdHdhcmUgcHJvdmlkZWQgdGhhdCBhbnkgcmVzZXJ2ZWQKbmFtZXMgYXJlIG5vdCB1c2VkIGJ5IGRlcml2YXRpdmUgd29ya3MuIFRoZSBmb250cyBhbmQgZGVyaXZhdGl2ZXMsCmhvd2V2ZXIsIGNhbm5vdCBiZSByZWxlYXNlZCB1bmRlciBhbnkgb3RoZXIgdHlwZSBvZiBsaWNlbnNlLiBUaGUKcmVxdWlyZW1lbnQgZm9yIGZvbnRzIHRvIHJlbWFpbiB1bmRlciB0aGlzIGxpY2Vuc2UgZG9lcyBub3QgYXBwbHkKdG8gYW55IGRvY3VtZW50IGNyZWF0ZWQgdXNpbmcgdGhlIGZvbnRzIG9yIHRoZWlyIGRlcml2YXRpdmVzLgoKREVGSU5JVElPTlMKIkZvbnQgU29mdHdhcmUiIHJlZmVycyB0byB0aGUgc2V0IG9mIGZpbGVzIHJlbGVhc2VkIGJ5IHRoZSBDb3B5cmlnaHQKSG9sZGVyKHMpIHVuZGVyIHRoaXMgbGljZW5zZSBhbmQgY2xlYXJseSBtYXJrZWQgYXMgc3VjaC4gVGhpcyBtYXkKaW5jbHVkZSBzb3VyY2UgZmlsZXMsIGJ1aWxkIHNjcmlwdHMgYW5kIGRvY3VtZW50YXRpb24uCgoiUmVzZXJ2ZWQgRm9udCBOYW1lIiByZWZlcnMgdG8gYW55IG5hbWVzIHNwZWNpZmllZCBhcyBzdWNoIGFmdGVyIHRoZQpjb3B5cmlnaHQgc3RhdGVtZW50KHMpLgoKIk9yaWdpbmFsIFZlcnNpb24iIHJlZmVycyB0byB0aGUgY29sbGVjdGlvbiBvZiBGb250IFNvZnR3YXJlIGNvbXBvbmVudHMgYXMKZGlzdHJpYnV0ZWQgYnkgdGhlIENvcHlyaWdodCBIb2xkZXIocykuCgoiTW9kaWZpZWQgVmVyc2lvbiIgcmVmZXJzIHRvIGFueSBkZXJpdmF0aXZlIG1hZGUgYnkgYWRkaW5nIHRvLCBkZWxldGluZywKb3Igc3Vic3RpdHV0aW5nIC0tIGluIHBhcnQgb3IgaW4gd2hvbGUgLS0gYW55IG9mIHRoZSBjb21wb25lbnRzIG9mIHRoZQpPcmlnaW5hbCBWZXJzaW9uLCBieSBjaGFuZ2luZyBmb3JtYXRzIG9yIGJ5IHBvcnRpbmcgdGhlIEZvbnQgU29mdHdhcmUgdG8gYQpuZXcgZW52aXJvbm1lbnQuCgoiQXV0aG9yIiByZWZlcnMgdG8gYW55IGRlc2lnbmVyLCBlbmdpbmVlciwgcHJvZ3JhbW1lciwgdGVjaG5pY2FsCndyaXRlciBvciBvdGhlciBwZXJzb24gd2hvIGNvbnRyaWJ1dGVkIHRvIHRoZSBGb250IFNvZnR3YXJlLgoKUEVSTUlTU0lPTiAmIENPTkRJVElPTlMKUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nCmEgY29weSBvZiB0aGUgRm9udCBTb2Z0d2FyZSwgdG8gdXNlLCBzdHVkeSwgY29weSwgbWVyZ2UsIGVtYmVkLCBtb2RpZnksCnJlZGlzdHJpYnV0ZSwgYW5kIHNlbGwgbW9kaWZpZWQgYW5kIHVubW9kaWZpZWQgY29waWVzIG9mIHRoZSBGb250ClNvZnR3YXJlLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKCjEpIE5laXRoZXIgdGhlIEZvbnQgU29mdHdhcmUgbm9yIGFueSBvZiBpdHMgaW5kaXZpZHVhbCBjb21wb25lbnRzLAppbiBPcmlnaW5hbCBvciBNb2RpZmllZCBWZXJzaW9ucywgbWF5IGJlIHNvbGQgYnkgaXRzZWxmLgoKMikgT3JpZ2luYWwgb3IgTW9kaWZpZWQgVmVyc2lvbnMgb2YgdGhlIEZvbnQgU29mdHdhcmUgbWF5IGJlIGJ1bmRsZWQsCnJlZGlzdHJpYnV0ZWQgYW5kL29yIHNvbGQgd2l0aCBhbnkgc29mdHdhcmUsIHByb3ZpZGVkIHRoYXQgZWFjaCBjb3B5CmNvbnRhaW5zIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIGxpY2Vuc2UuIFRoZXNlIGNhbiBiZQppbmNsdWRlZCBlaXRoZXIgYXMgc3RhbmQtYWxvbmUgdGV4dCBmaWxlcywgaHVtYW4tcmVhZGFibGUgaGVhZGVycyBvcgppbiB0aGUgYXBwcm9wcmlhdGUgbWFjaGluZS1yZWFkYWJsZSBtZXRhZGF0YSBmaWVsZHMgd2l0aGluIHRleHQgb3IKYmluYXJ5IGZpbGVzIGFzIGxvbmcgYXMgdGhvc2UgZmllbGRzIGNhbiBiZSBlYXNpbHkgdmlld2VkIGJ5IHRoZSB1c2VyLgoKMykgTm8gTW9kaWZpZWQgVmVyc2lvbiBvZiB0aGUgRm9udCBTb2Z0d2FyZSBtYXkgdXNlIHRoZSBSZXNlcnZlZCBGb250Ck5hbWUocykgdW5sZXNzIGV4cGxpY2l0IHdyaXR0ZW4gcGVybWlzc2lvbiBpcyBncmFudGVkIGJ5IHRoZSBjb3JyZXNwb25kaW5nCkNvcHlyaWdodCBIb2xkZXIuIFRoaXMgcmVzdHJpY3Rpb24gb25seSBhcHBsaWVzIHRvIHRoZSBwcmltYXJ5IGZvbnQgbmFtZSBhcwpwcmVzZW50ZWQgdG8gdGhlIHVzZXJzLgoKNCkgVGhlIG5hbWUocykgb2YgdGhlIENvcHlyaWdodCBIb2xkZXIocykgb3IgdGhlIEF1dGhvcihzKSBvZiB0aGUgRm9udApTb2Z0d2FyZSBzaGFsbCBub3QgYmUgdXNlZCB0byBwcm9tb3RlLCBlbmRvcnNlIG9yIGFkdmVydGlzZSBhbnkKTW9kaWZpZWQgVmVyc2lvbiwgZXhjZXB0IHRvIGFja25vd2xlZGdlIHRoZSBjb250cmlidXRpb24ocykgb2YgdGhlCkNvcHlyaWdodCBIb2xkZXIocykgYW5kIHRoZSBBdXRob3Iocykgb3Igd2l0aCB0aGVpciBleHBsaWNpdCB3cml0dGVuCnBlcm1pc3Npb24uCgo1KSBUaGUgRm9udCBTb2Z0d2FyZSwgbW9kaWZpZWQgb3IgdW5tb2RpZmllZCwgaW4gcGFydCBvciBpbiB3aG9sZSwKbXVzdCBiZSBkaXN0cmlidXRlZCBlbnRpcmVseSB1bmRlciB0aGlzIGxpY2Vuc2UsIGFuZCBtdXN0IG5vdCBiZQpkaXN0cmlidXRlZCB1bmRlciBhbnkgb3RoZXIgbGljZW5zZS4gVGhlIHJlcXVpcmVtZW50IGZvciBmb250cyB0bwpyZW1haW4gdW5kZXIgdGhpcyBsaWNlbnNlIGRvZXMgbm90IGFwcGx5IHRvIGFueSBkb2N1bWVudCBjcmVhdGVkCnVzaW5nIHRoZSBGb250IFNvZnR3YXJlLgoKVEVSTUlOQVRJT04KVGhpcyBsaWNlbnNlIGJlY29tZXMgbnVsbCBhbmQgdm9pZCBpZiBhbnkgb2YgdGhlIGFib3ZlIGNvbmRpdGlvbnMgYXJlCm5vdCBtZXQuCgpESVNDTEFJTUVSClRIRSBGT05UIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsCkVYUFJFU1MgT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBBTlkgV0FSUkFOVElFUyBPRgpNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQKT0YgQ09QWVJJR0hULCBQQVRFTlQsIFRSQURFTUFSSywgT1IgT1RIRVIgUklHSFQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwKSU5DTFVESU5HIEFOWSBHRU5FUkFMLCBTUEVDSUFMLCBJTkRJUkVDVCwgSU5DSURFTlRBTCwgT1IgQ09OU0VRVUVOVElBTApEQU1BR0VTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcKRlJPTSwgT1VUIE9GIFRIRSBVU0UgT1IgSU5BQklMSVRZIFRPIFVTRSBUSEUgRk9OVCBTT0ZUV0FSRSBPUiBGUk9NCk9USEVSIERFQUxJTkdTIElOIFRIRSBGT05UIFNPRlRXQVJFLgAAaAB0AHQAcAA6AC8ALwBzAGMAcgBpAHAAdABzAC4AcwBpAGwALgBvAHIAZwAvAE8ARgBMAABodHRwOi8vc2NyaXB0cy5zaWwub3JnL09GTAAAAAACAAAAAAAA/+kAMwAAAAEAAAAAAAAAAAAAAAAAAAAAAi8AAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAAxADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0ATgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAQIBAwCjAIQAhQC9AJYA6ACGAI4AiwCdAKkApAEEAIoA2gCDAJMBBQEGAI0BBwCIAMMA3gEIAJ4AqgD1APQA9gCiAK0AyQDHAK4AYgBjAJAAZADLAGUAyADKAM8AzADNAM4A6QBmANMA0ADRAK8AZwDwAJEA1gDUANUAaADrAO0AiQBqAGkAawBtAGwAbgCgAG8AcQBwAHIAcwB1AHQAdgB3AOoAeAB6AHkAewB9AHwAuAChAH8AfgCAAIEA7ADuALoBCQEKAQsBDAENAQ4A/QD+AQ8BEAERARIA/wEAARMBFAEVAQEBFgEXARgBGQEaARsBHAEdAR4BHwEgASEA+AD5ASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEA+gDXATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAOIA4wFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwCwALEBUAFRAVIBUwFUAVUBVgFXAVgBWQD7APwA5ADlAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8AuwFwAXEBcgFzAOYA5wF0AKYA2ADhAXUBdgF3AXgA2wDcAN0A4ADZAN8BeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQBhQGGAYcBiACoAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsAnwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQCXAa4BrwGwAJsBsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHfAeAB4QHiAeMB5AHlAeYB5wHoAekB6gHrAewB7QHuAe8B8AHxAfIB8wH0AfUB9gH3AfgB+QH6AfsB/AH9Af4B/wIAAgECAgIDAgQCBQIGAgcCCAIJAgoCCwIMAg0CDgIPAhACEQISAhMCFAIVAhYCFwIYAhkCGgIbAhwCHQIeALIAswIfALYAtwDEALQAtQDFAIIAwgCHAKsAxgC+AL8AvAIgAiECIgCMAiMCJAIlAiYAmAInAJoAmQClAJIAnACnAI8AlACVAigCKQIqAisAuQIsAi0CLgIvAjACMQIyAjMCNAI1AjYCNwd1bmkwMDdGB3VuaTAwQTAHdW5pMDBBRAd1bmkwMEIyB3VuaTAwQjMHdW5pMDBCNQd1bmkwMEI5B0FtYWNyb24HYW1hY3JvbgZBYnJldmUGYWJyZXZlB0FvZ29uZWsHYW9nb25lawtDY2lyY3VtZmxleAtjY2lyY3VtZmxleApDZG90YWNjZW50CmNkb3RhY2NlbnQGRGNhcm9uBmRjYXJvbgZEY3JvYXQHRW1hY3JvbgdlbWFjcm9uBkVicmV2ZQZlYnJldmUKRWRvdGFjY2VudAplZG90YWNjZW50B0VvZ29uZWsHZW9nb25lawZFY2Fyb24GZWNhcm9uC0djaXJjdW1mbGV4C2djaXJjdW1mbGV4Ckdkb3RhY2NlbnQKZ2RvdGFjY2VudAxHY29tbWFhY2NlbnQMZ2NvbW1hYWNjZW50C0hjaXJjdW1mbGV4C2hjaXJjdW1mbGV4BEhiYXIEaGJhcgZJdGlsZGUGaXRpbGRlB0ltYWNyb24HaW1hY3JvbgZJYnJldmUGaWJyZXZlB0lvZ29uZWsHaW9nb25lawJJSgJpagtKY2lyY3VtZmxleAtqY2lyY3VtZmxleAxLY29tbWFhY2NlbnQMa2NvbW1hYWNjZW50DGtncmVlbmxhbmRpYwZMYWN1dGUGbGFjdXRlDExjb21tYWFjY2VudAxsY29tbWFhY2NlbnQGTGNhcm9uBmxjYXJvbgRMZG90BGxkb3QGTmFjdXRlBm5hY3V0ZQxOY29tbWFhY2NlbnQMbmNvbW1hYWNjZW50Bk5jYXJvbgZuY2Fyb24LbmFwb3N0cm9waGUDRW5nA2VuZwdPbWFjcm9uB29tYWNyb24GT2JyZXZlBm9icmV2ZQ1PaHVuZ2FydW1sYXV0DW9odW5nYXJ1bWxhdXQGUmFjdXRlBnJhY3V0ZQxSY29tbWFhY2NlbnQMcmNvbW1hYWNjZW50BlJjYXJvbgZyY2Fyb24GU2FjdXRlBnNhY3V0ZQtTY2lyY3VtZmxleAtzY2lyY3VtZmxleAxUY29tbWFhY2NlbnQMdGNvbW1hYWNjZW50BlRjYXJvbgZ0Y2Fyb24EVGJhcgR0YmFyBlV0aWxkZQZ1dGlsZGUHVW1hY3Jvbgd1bWFjcm9uBlVicmV2ZQZ1YnJldmUFVXJpbmcFdXJpbmcNVWh1bmdhcnVtbGF1dA11aHVuZ2FydW1sYXV0B1VvZ29uZWsHdW9nb25lawtXY2lyY3VtZmxleAt3Y2lyY3VtZmxleAtZY2lyY3VtZmxleAt5Y2lyY3VtZmxleAZaYWN1dGUGemFjdXRlClpkb3RhY2NlbnQKemRvdGFjY2VudAVsb25ncwd1bmkwMkM5B3VuaTAyQ0EHdW5pMDJDQgd1bmkwMkQ3B3VuaTAzN0EHdW5pMDM3RQV0b25vcw1kaWVyZXNpc3Rvbm9zCkFscGhhdG9ub3MJYW5vdGVsZWlhDEVwc2lsb250b25vcwhFdGF0b25vcwlJb3RhdG9ub3MMT21pY3JvbnRvbm9zDFVwc2lsb250b25vcwpPbWVnYXRvbm9zEWlvdGFkaWVyZXNpc3Rvbm9zBUFscGhhBEJldGEFR2FtbWEHRXBzaWxvbgRaZXRhA0V0YQVUaGV0YQRJb3RhBUthcHBhBkxhbWJkYQJNdQJOdQJYaQdPbWljcm9uAlBpA1JobwVTaWdtYQNUYXUHVXBzaWxvbgNQaGkDQ2hpA1BzaQxJb3RhZGllcmVzaXMPVXBzaWxvbmRpZXJlc2lzCmFscGhhdG9ub3MMZXBzaWxvbnRvbm9zCGV0YXRvbm9zCWlvdGF0b25vcxR1cHNpbG9uZGllcmVzaXN0b25vcwVhbHBoYQRiZXRhBWdhbW1hBWRlbHRhB2Vwc2lsb24EemV0YQNldGEFdGhldGEEaW90YQVrYXBwYQZsYW1iZGECbnUCeGkHb21pY3JvbgNyaG8Gc2lnbWExBXNpZ21hA3RhdQd1cHNpbG9uA3BoaQNjaGkDcHNpBW9tZWdhDGlvdGFkaWVyZXNpcw91cHNpbG9uZGllcmVzaXMMb21pY3JvbnRvbm9zDHVwc2lsb250b25vcwpvbWVnYXRvbm9zB3VuaTA0MDAJYWZpaTEwMDIzCWFmaWkxMDA1MQlhZmlpMTAwNTIJYWZpaTEwMDUzCWFmaWkxMDA1NAlhZmlpMTAwNTUJYWZpaTEwMDU2CWFmaWkxMDA1NwlhZmlpMTAwNTgJYWZpaTEwMDU5CWFmaWkxMDA2MAlhZmlpMTAwNjEHdW5pMDQwRAlhZmlpMTAwNjIJYWZpaTEwMTQ1CWFmaWkxMDAxNwlhZmlpMTAwMTgJYWZpaTEwMDE5CWFmaWkxMDAyMAlhZmlpMTAwMjEJYWZpaTEwMDIyCWFmaWkxMDAyNAlhZmlpMTAwMjUJYWZpaTEwMDI2CWFmaWkxMDAyNwlhZmlpMTAwMjgJYWZpaTEwMDI5CWFmaWkxMDAzMAlhZmlpMTAwMzEJYWZpaTEwMDMyCWFmaWkxMDAzMwlhZmlpMTAwMzQJYWZpaTEwMDM1CWFmaWkxMDAzNglhZmlpMTAwMzcJYWZpaTEwMDM4CWFmaWkxMDAzOQlhZmlpMTAwNDAJYWZpaTEwMDQxCWFmaWkxMDA0MglhZmlpMTAwNDMJYWZpaTEwMDQ0CWFmaWkxMDA0NQlhZmlpMTAwNDYJYWZpaTEwMDQ3CWFmaWkxMDA0OAlhZmlpMTAwNDkJYWZpaTEwMDY1CWFmaWkxMDA2NglhZmlpMTAwNjcJYWZpaTEwMDY4CWFmaWkxMDA2OQlhZmlpMTAwNzAJYWZpaTEwMDcyCWFmaWkxMDA3MwlhZmlpMTAwNzQJYWZpaTEwMDc1CWFmaWkxMDA3NglhZmlpMTAwNzcJYWZpaTEwMDc4CWFmaWkxMDA3OQlhZmlpMTAwODAJYWZpaTEwMDgxCWFmaWkxMDA4MglhZmlpMTAwODMJYWZpaTEwMDg0CWFmaWkxMDA4NQlhZmlpMTAwODYJYWZpaTEwMDg3CWFmaWkxMDA4OAlhZmlpMTAwODkJYWZpaTEwMDkwCWFmaWkxMDA5MQlhZmlpMTAwOTIJYWZpaTEwMDkzCWFmaWkxMDA5NAlhZmlpMTAwOTUJYWZpaTEwMDk2CWFmaWkxMDA5Nwd1bmkwNDUwCWFmaWkxMDA3MQlhZmlpMTAwOTkJYWZpaTEwMTAwCWFmaWkxMDEwMQlhZmlpMTAxMDIJYWZpaTEwMTAzCWFmaWkxMDEwNAlhZmlpMTAxMDUJYWZpaTEwMTA2CWFmaWkxMDEwNwlhZmlpMTAxMDgJYWZpaTEwMTA5B3VuaTA0NUQJYWZpaTEwMTEwCWFmaWkxMDE5MwlhZmlpMDAyMDgERXVybwd1bmkyMEFGCWFmaWk2MTM1MglhcnJvd2xlZnQHYXJyb3d1cAphcnJvd3JpZ2h0CWFycm93ZG93bgd1bmkyMjA2B3RyaWFndXAHdW5pMjVCNgd0cmlhZ2RuB3VuaTI1QzAHdW5pMjYwNQd1bmkyNjA2BXNwYWRlBGNsdWIFaGVhcnQHZGlhbW9uZAttdXNpY2Fsbm90ZQd1bmlGMTAwB3VuaUYxMDEHdW5pRjhGRgd1bmlGQjAxB3VuaUZCMDIAAAAAAf//AAIAAQAAAA4AAAAYACAAAAACAAEAAQIuAAEABAAAAAIAAAABAAAAAQAAAAAAAQAAAADMPaLPAAAAAMobHN4AAAAAzLYldw==","size":24},"glow":{"ratios":[0,255],"distance":0,"blurX":2,"quality":3,"blurY":2,"strength":2,"colors":[16711680,16711680],"angle":45,"alphas":[0,1],"glowEnabled":false},"stroke":{"pixelHinting":false,"jointStyle":"miter","gradientType":"linear","miterLimit":1,"gradientAlphas":[1,1,1,1],"gradientColors":[8872704,5651456,5651456,5651456],"xOffset":0,"gradientRotation":90,"yOffset":0,"fillType":"gradientFill","textureScale":1,"strokeEnabled":false,"gradientRatios":[0,250.2336448598131,250.2336448598131,255],"size":2},"fill":{"distanceFieldSpread":16,"distanceFieldType":"Type 1","gradientRatios":[0,255],"gradientType":"linear","gradientAlphas":[1,1],"gradientColors":[16777215,16777215],"xOffset":0,"gradientRotation":90,"yOffset":0,"fillType":"gradientFill","distanceFieldEnabled":false,"textureScale":1,"distanceFieldColor":16777215,"distanceFieldDownscale":4},"shadow":{"distance":4,"alpha":1,"blurX":4,"quality":1,"blurY":4,"color":0,"strength":1,"shadowEnabled":false,"angle":45},"background":{"color":16777215,"alpha":0},"bevel":{"strength":1,"highlightColor":16777215,"highlightAlpha":1,"shadowColor":0,"shadowAlpha":1,"distance":4,"blurX":4,"quality":1,"blurY":4,"type":"inner","bevelEnabled":false,"angle":45}} \ No newline at end of file diff --git a/Games/Contra-Shooter/data/fnt/PressStart2P.png b/Games/Contra-Shooter/data/fnt/PressStart2P.png new file mode 100644 index 0000000000..73766690aa Binary files /dev/null and b/Games/Contra-Shooter/data/fnt/PressStart2P.png differ diff --git a/Games/Contra-Shooter/data/fnt/PressStart2P.ttf b/Games/Contra-Shooter/data/fnt/PressStart2P.ttf new file mode 100644 index 0000000000..98044e935e Binary files /dev/null and b/Games/Contra-Shooter/data/fnt/PressStart2P.ttf differ diff --git a/Games/Contra-Shooter/data/img/gui/icon_badge_24_48.png b/Games/Contra-Shooter/data/img/gui/icon_badge_24_48.png new file mode 100644 index 0000000000..871702a3bd Binary files /dev/null and b/Games/Contra-Shooter/data/img/gui/icon_badge_24_48.png differ diff --git a/Games/Contra-Shooter/data/img/gui/icon_select_30_25.png b/Games/Contra-Shooter/data/img/gui/icon_select_30_25.png new file mode 100644 index 0000000000..597987de88 Binary files /dev/null and b/Games/Contra-Shooter/data/img/gui/icon_select_30_25.png differ diff --git a/Games/Contra-Shooter/data/img/gui/img_captured.png b/Games/Contra-Shooter/data/img/gui/img_captured.png new file mode 100644 index 0000000000..629990969d Binary files /dev/null and b/Games/Contra-Shooter/data/img/gui/img_captured.png differ diff --git a/Games/Contra-Shooter/data/img/gui/img_neslogo.png b/Games/Contra-Shooter/data/img/gui/img_neslogo.png new file mode 100644 index 0000000000..914c130fb9 Binary files /dev/null and b/Games/Contra-Shooter/data/img/gui/img_neslogo.png differ diff --git a/Games/Contra-Shooter/data/img/gui/img_splashpage.png b/Games/Contra-Shooter/data/img/gui/img_splashpage.png new file mode 100644 index 0000000000..6b110ca5a2 Binary files /dev/null and b/Games/Contra-Shooter/data/img/gui/img_splashpage.png differ diff --git a/Games/Contra-Shooter/data/img/map/contra-tile-map.png b/Games/Contra-Shooter/data/img/map/contra-tile-map.png new file mode 100644 index 0000000000..6182a13966 Binary files /dev/null and b/Games/Contra-Shooter/data/img/map/contra-tile-map.png differ diff --git a/Games/Contra-Shooter/data/img/sprite/bad-guys.json b/Games/Contra-Shooter/data/img/sprite/bad-guys.json new file mode 100644 index 0000000000..3e3f66aa06 --- /dev/null +++ b/Games/Contra-Shooter/data/img/sprite/bad-guys.json @@ -0,0 +1,121 @@ +{"frames": [ + { + "filename": "BadGuys_01.png", + "frame": {"x":33,"y":0,"w":24,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":31}, + "sourceSize": {"w":24,"h":31} + }, + { + "filename": "BadGuys_02.png", + "frame": {"x":30,"y":68,"w":24,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":31}, + "sourceSize": {"w":24,"h":31} + }, + { + "filename": "BadGuys_03.png", + "frame": {"x":31,"y":34,"w":24,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":32}, + "sourceSize": {"w":24,"h":32} + }, + { + "filename": "BadGuys_04.png", + "frame": {"x":77,"y":0,"w":18,"h":38}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":38}, + "sourceSize": {"w":18,"h":38} + }, + { + "filename": "BadGuys_05.png", + "frame": {"x":76,"y":68,"w":18,"h":39}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":39}, + "sourceSize": {"w":18,"h":39} + }, + { + "filename": "BadGuys_17.png", + "frame": {"x":96,"y":40,"w":17,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":32}, + "sourceSize": {"w":17,"h":32} + }, + { + "filename": "BadGuys_18.png", + "frame": {"x":57,"y":33,"w":18,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":33}, + "sourceSize": {"w":18,"h":33} + }, + { + "filename": "BadGuys_19.png", + "frame": {"x":96,"y":74,"w":17,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":31}, + "sourceSize": {"w":17,"h":31} + }, + { + "filename": "BadGuys_20.png", + "frame": {"x":97,"y":0,"w":16,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":32}, + "sourceSize": {"w":16,"h":32} + }, + { + "filename": "BadGuys_21.png", + "frame": {"x":56,"y":68,"w":18,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":33}, + "sourceSize": {"w":18,"h":33} + }, + { + "filename": "BadGuys_22.png", + "frame": {"x":0,"y":96,"w":18,"h":28}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":28}, + "sourceSize": {"w":18,"h":28} + }, + { + "filename": "BadGuys_93.png", + "frame": {"x":0,"y":34,"w":29,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":29,"h":32}, + "sourceSize": {"w":29,"h":32} + }, + { + "filename": "BadGuys_94.png", + "frame": {"x":0,"y":0,"w":31,"h":32}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":31,"h":32}, + "sourceSize": {"w":31,"h":32} + }, + { + "filename": "BadGuys_95.png", + "frame": {"x":0,"y":68,"w":28,"h":26}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":28,"h":26}, + "sourceSize": {"w":28,"h":26} + }, + {"EOL":"true"} +], +"meta": { + "app": "ShoeBox", + "exporter": "melonJS", + "size": {"w":128,"h":128} +} +} \ No newline at end of file diff --git a/Games/Contra-Shooter/data/img/sprite/bad-guys.png b/Games/Contra-Shooter/data/img/sprite/bad-guys.png new file mode 100644 index 0000000000..bad714b4bc Binary files /dev/null and b/Games/Contra-Shooter/data/img/sprite/bad-guys.png differ diff --git a/Games/Contra-Shooter/data/img/sprite/bill-lance.json b/Games/Contra-Shooter/data/img/sprite/bill-lance.json new file mode 100644 index 0000000000..d2bf2df607 --- /dev/null +++ b/Games/Contra-Shooter/data/img/sprite/bill-lance.json @@ -0,0 +1,969 @@ +{"frames": [ + { + "filename": "Bill_01.png", + "frame": {"x":335,"y":47,"w":14,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":45}, + "sourceSize": {"w":14,"h":45} + }, + { + "filename": "Bill_02.png", + "frame": {"x":337,"y":94,"w":14,"h":46}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":46}, + "sourceSize": {"w":14,"h":46} + }, + { + "filename": "Bill_03.png", + "frame": {"x":52,"y":200,"w":24,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":33}, + "sourceSize": {"w":24,"h":33} + }, + { + "filename": "Bill_04.png", + "frame": {"x":27,"y":164,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + { + "filename": "Bill_05.png", + "frame": {"x":36,"y":0,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + { + "filename": "Bill_06.png", + "frame": {"x":36,"y":72,"w":24,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":33}, + "sourceSize": {"w":24,"h":33} + }, + { + "filename": "Bill_07.png", + "frame": {"x":321,"y":130,"w":14,"h":46}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":46}, + "sourceSize": {"w":14,"h":46} + }, + { + "filename": "Bill_08.png", + "frame": {"x":336,"y":178,"w":14,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":45}, + "sourceSize": {"w":14,"h":45} + }, + { + "filename": "Bill_09.png", + "frame": {"x":129,"y":121,"w":21,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":34}, + "sourceSize": {"w":21,"h":34} + }, + { + "filename": "Bill_10.png", + "frame": {"x":136,"y":37,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Bill_11.png", + "frame": {"x":283,"y":133,"w":17,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":36}, + "sourceSize": {"w":17,"h":36} + }, + { + "filename": "Bill_12.png", + "frame": {"x":302,"y":133,"w":17,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":36}, + "sourceSize": {"w":17,"h":36} + }, + { + "filename": "Bill_13.png", + "frame": {"x":152,"y":159,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Bill_14.png", + "frame": {"x":152,"y":197,"w":21,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":34}, + "sourceSize": {"w":21,"h":34} + }, + { + "filename": "Bill_15.png", + "frame": {"x":104,"y":126,"w":23,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":33}, + "sourceSize": {"w":23,"h":33} + }, + { + "filename": "Bill_16.png", + "frame": {"x":78,"y":213,"w":23,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":35}, + "sourceSize": {"w":23,"h":35} + }, + { + "filename": "Bill_17.png", + "frame": {"x":176,"y":73,"w":21,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":35}, + "sourceSize": {"w":21,"h":35} + }, + { + "filename": "Bill_18.png", + "frame": {"x":182,"y":36,"w":21,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":35}, + "sourceSize": {"w":21,"h":35} + }, + { + "filename": "Bill_19.png", + "frame": {"x":103,"y":169,"w":23,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":35}, + "sourceSize": {"w":23,"h":35} + }, + { + "filename": "Bill_20.png", + "frame": {"x":88,"y":0,"w":23,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":33}, + "sourceSize": {"w":23,"h":33} + }, + { + "filename": "Bill_21.png", + "frame": {"x":282,"y":196,"w":18,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":36}, + "sourceSize": {"w":18,"h":36} + }, + { + "filename": "Bill_22.png", + "frame": {"x":198,"y":110,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Bill_23.png", + "frame": {"x":128,"y":161,"w":22,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":22,"h":31}, + "sourceSize": {"w":22,"h":31} + }, + { + "filename": "Bill_24.png", + "frame": {"x":283,"y":72,"w":18,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":34}, + "sourceSize": {"w":18,"h":34} + }, + { + "filename": "Bill_25.png", + "frame": {"x":199,"y":73,"w":20,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":34}, + "sourceSize": {"w":20,"h":34} + }, + { + "filename": "Bill_26.png", + "frame": {"x":220,"y":186,"w":20,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":34}, + "sourceSize": {"w":20,"h":34} + }, + { + "filename": "Bill_27.png", + "frame": {"x":269,"y":36,"w":18,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":34}, + "sourceSize": {"w":18,"h":34} + }, + { + "filename": "Bill_28.png", + "frame": {"x":129,"y":88,"w":22,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":22,"h":31}, + "sourceSize": {"w":22,"h":31} + }, + { + "filename": "Bill_29.png", + "frame": {"x":198,"y":148,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Bill_30.png", + "frame": {"x":263,"y":108,"w":18,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":36}, + "sourceSize": {"w":18,"h":36} + }, + { + "filename": "Bill_31.png", + "frame": {"x":283,"y":171,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Bill_32.png", + "frame": {"x":194,"y":229,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Bill_33.png", + "frame": {"x":300,"y":234,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Bill_34.png", + "frame": {"x":205,"y":36,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Bill_35.png", + "frame": {"x":0,"y":26,"w":34,"h":17}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":17}, + "sourceSize": {"w":34,"h":17} + }, + { + "filename": "Bill_36.png", + "frame": {"x":0,"y":45,"w":34,"h":17}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":17}, + "sourceSize": {"w":34,"h":17} + }, + { + "filename": "Bill_37.png", + "frame": {"x":216,"y":229,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Bill_38.png", + "frame": {"x":221,"y":162,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Bill_39.png", + "frame": {"x":205,"y":54,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Bill_40.png", + "frame": {"x":282,"y":234,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Bill_41.png", + "frame": {"x":0,"y":0,"w":34,"h":11}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":11}, + "sourceSize": {"w":34,"h":11} + }, + { + "filename": "Bill_42.png", + "frame": {"x":247,"y":47,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Bill_43.png", + "frame": {"x":79,"y":109,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Bill_44.png", + "frame": {"x":263,"y":146,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Bill_45.png", + "frame": {"x":79,"y":129,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Bill_46.png", + "frame": {"x":79,"y":149,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Bill_47.png", + "frame": {"x":263,"y":171,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Bill_48.png", + "frame": {"x":88,"y":35,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Bill_49.png", + "frame": {"x":262,"y":197,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Bill_50.png", + "frame": {"x":0,"y":115,"w":34,"h":11}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":11}, + "sourceSize": {"w":34,"h":11} + }, + { + "filename": "Bill_51.png", + "frame": {"x":87,"y":71,"w":23,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":16}, + "sourceSize": {"w":23,"h":16} + }, + { + "filename": "Bill_52.png", + "frame": {"x":62,"y":89,"w":12,"h":13}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":13}, + "sourceSize": {"w":12,"h":13} + }, + { + "filename": "Bill_53.png", + "frame": {"x":36,"y":107,"w":12,"h":13}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":13}, + "sourceSize": {"w":12,"h":13} + }, + { + "filename": "Bill_54.png", + "frame": {"x":62,"y":71,"w":23,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":16}, + "sourceSize": {"w":23,"h":16} + }, + { + "filename": "Bill_55.png", + "frame": {"x":221,"y":115,"w":19,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":45}, + "sourceSize": {"w":19,"h":45} + }, + { + "filename": "Bill_56.png", + "frame": {"x":0,"y":128,"w":25,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":45}, + "sourceSize": {"w":25,"h":45} + }, + { + "filename": "Bill_57.png", + "frame": {"x":242,"y":115,"w":19,"h":39}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":39}, + "sourceSize": {"w":19,"h":39} + }, + { + "filename": "Bill_58.png", + "frame": {"x":198,"y":186,"w":20,"h":41}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":41}, + "sourceSize": {"w":20,"h":41} + }, + { + "filename": "Bill_59.png", + "frame": {"x":26,"y":222,"w":24,"h":22}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":22}, + "sourceSize": {"w":24,"h":22} + }, + { + "filename": "Bill_60.png", + "frame": {"x":36,"y":36,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + { + "filename": "Lance_01.png", + "frame": {"x":325,"y":0,"w":14,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":45}, + "sourceSize": {"w":14,"h":45} + }, + { + "filename": "Lance_02.png", + "frame": {"x":319,"y":82,"w":14,"h":46}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":46}, + "sourceSize": {"w":14,"h":46} + }, + { + "filename": "Lance_03.png", + "frame": {"x":62,"y":36,"w":24,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":33}, + "sourceSize": {"w":24,"h":33} + }, + { + "filename": "Lance_04.png", + "frame": {"x":53,"y":142,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + { + "filename": "Lance_05.png", + "frame": {"x":62,"y":0,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + { + "filename": "Lance_06.png", + "frame": {"x":53,"y":107,"w":24,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":33}, + "sourceSize": {"w":24,"h":33} + }, + { + "filename": "Lance_07.png", + "frame": {"x":303,"y":82,"w":14,"h":46}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":46}, + "sourceSize": {"w":14,"h":46} + }, + { + "filename": "Lance_08.png", + "frame": {"x":320,"y":209,"w":14,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":14,"h":45}, + "sourceSize": {"w":14,"h":45} + }, + { + "filename": "Lance_09.png", + "frame": {"x":138,"y":0,"w":21,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":34}, + "sourceSize": {"w":21,"h":34} + }, + { + "filename": "Lance_10.png", + "frame": {"x":152,"y":121,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Lance_11.png", + "frame": {"x":289,"y":0,"w":17,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":36}, + "sourceSize": {"w":17,"h":36} + }, + { + "filename": "Lance_12.png", + "frame": {"x":302,"y":171,"w":17,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":17,"h":36}, + "sourceSize": {"w":17,"h":36} + }, + { + "filename": "Lance_13.png", + "frame": {"x":153,"y":75,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Lance_14.png", + "frame": {"x":161,"y":0,"w":21,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":34}, + "sourceSize": {"w":21,"h":34} + }, + { + "filename": "Lance_15.png", + "frame": {"x":78,"y":178,"w":23,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":33}, + "sourceSize": {"w":23,"h":33} + }, + { + "filename": "Lance_16.png", + "frame": {"x":113,"y":0,"w":23,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":35}, + "sourceSize": {"w":23,"h":35} + }, + { + "filename": "Lance_17.png", + "frame": {"x":159,"y":36,"w":21,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":35}, + "sourceSize": {"w":21,"h":35} + }, + { + "filename": "Lance_18.png", + "frame": {"x":175,"y":113,"w":21,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":35}, + "sourceSize": {"w":21,"h":35} + }, + { + "filename": "Lance_19.png", + "frame": {"x":104,"y":89,"w":23,"h":35}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":35}, + "sourceSize": {"w":23,"h":35} + }, + { + "filename": "Lance_20.png", + "frame": {"x":103,"y":206,"w":23,"h":33}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":33}, + "sourceSize": {"w":23,"h":33} + }, + { + "filename": "Lance_21.png", + "frame": {"x":249,"y":0,"w":18,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":36}, + "sourceSize": {"w":18,"h":36} + }, + { + "filename": "Lance_22.png", + "frame": {"x":175,"y":150,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Lance_23.png", + "frame": {"x":112,"y":55,"w":22,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":22,"h":31}, + "sourceSize": {"w":22,"h":31} + }, + { + "filename": "Lance_24.png", + "frame": {"x":263,"y":72,"w":18,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":34}, + "sourceSize": {"w":18,"h":34} + }, + { + "filename": "Lance_25.png", + "frame": {"x":206,"y":0,"w":20,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":34}, + "sourceSize": {"w":20,"h":34} + }, + { + "filename": "Lance_26.png", + "frame": {"x":184,"y":0,"w":20,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":34}, + "sourceSize": {"w":20,"h":34} + }, + { + "filename": "Lance_27.png", + "frame": {"x":269,"y":0,"w":18,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":34}, + "sourceSize": {"w":18,"h":34} + }, + { + "filename": "Lance_28.png", + "frame": {"x":128,"y":194,"w":22,"h":31}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":22,"h":31}, + "sourceSize": {"w":22,"h":31} + }, + { + "filename": "Lance_29.png", + "frame": {"x":175,"y":188,"w":21,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":36}, + "sourceSize": {"w":21,"h":36} + }, + { + "filename": "Lance_30.png", + "frame": {"x":242,"y":197,"w":18,"h":36}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":36}, + "sourceSize": {"w":18,"h":36} + }, + { + "filename": "Lance_31.png", + "frame": {"x":289,"y":38,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Lance_32.png", + "frame": {"x":172,"y":233,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Lance_33.png", + "frame": {"x":303,"y":60,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Lance_34.png", + "frame": {"x":113,"y":37,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Lance_35.png", + "frame": {"x":0,"y":77,"w":34,"h":17}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":17}, + "sourceSize": {"w":34,"h":17} + }, + { + "filename": "Lance_36.png", + "frame": {"x":0,"y":96,"w":34,"h":17}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":17}, + "sourceSize": {"w":34,"h":17} + }, + { + "filename": "Lance_37.png", + "frame": {"x":150,"y":233,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Lance_38.png", + "frame": {"x":302,"y":209,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Lance_39.png", + "frame": {"x":128,"y":227,"w":20,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":16}, + "sourceSize": {"w":20,"h":16} + }, + { + "filename": "Lance_40.png", + "frame": {"x":307,"y":38,"w":16,"h":20}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":20}, + "sourceSize": {"w":16,"h":20} + }, + { + "filename": "Lance_41.png", + "frame": {"x":0,"y":64,"w":34,"h":11}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":11}, + "sourceSize": {"w":34,"h":11} + }, + { + "filename": "Lance_42.png", + "frame": {"x":283,"y":108,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Lance_43.png", + "frame": {"x":52,"y":235,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Lance_44.png", + "frame": {"x":243,"y":72,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Lance_45.png", + "frame": {"x":27,"y":200,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Lance_46.png", + "frame": {"x":53,"y":178,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Lance_47.png", + "frame": {"x":227,"y":47,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Lance_48.png", + "frame": {"x":79,"y":89,"w":23,"h":18}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":18}, + "sourceSize": {"w":23,"h":18} + }, + { + "filename": "Lance_49.png", + "frame": {"x":262,"y":222,"w":18,"h":23}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":18,"h":23}, + "sourceSize": {"w":18,"h":23} + }, + { + "filename": "Lance_50.png", + "frame": {"x":0,"y":13,"w":34,"h":11}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":34,"h":11}, + "sourceSize": {"w":34,"h":11} + }, + { + "filename": "Lance_51.png", + "frame": {"x":87,"y":71,"w":23,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":16}, + "sourceSize": {"w":23,"h":16} + }, + { + "filename": "Lance_52.png", + "frame": {"x":62,"y":89,"w":12,"h":13}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":13}, + "sourceSize": {"w":12,"h":13} + }, + { + "filename": "Lance_53.png", + "frame": {"x":36,"y":107,"w":12,"h":13}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":12,"h":13}, + "sourceSize": {"w":12,"h":13} + }, + { + "filename": "Lance_54.png", + "frame": {"x":62,"y":71,"w":23,"h":16}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":23,"h":16}, + "sourceSize": {"w":23,"h":16} + }, + { + "filename": "Lance_55.png", + "frame": {"x":228,"y":0,"w":19,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":45}, + "sourceSize": {"w":19,"h":45} + }, + { + "filename": "Lance_56.png", + "frame": {"x":0,"y":175,"w":25,"h":45}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":45}, + "sourceSize": {"w":25,"h":45} + }, + { + "filename": "Lance_57.png", + "frame": {"x":242,"y":156,"w":19,"h":39}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":39}, + "sourceSize": {"w":19,"h":39} + }, + { + "filename": "Lance_58.png", + "frame": {"x":221,"y":72,"w":20,"h":41}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":20,"h":41}, + "sourceSize": {"w":20,"h":41} + }, + { + "filename": "Lance_59.png", + "frame": {"x":0,"y":222,"w":24,"h":22}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":22}, + "sourceSize": {"w":24,"h":22} + }, + { + "filename": "Lance_60.png", + "frame": {"x":27,"y":128,"w":24,"h":34}, + "rotated": false, + "trimmed": true, + "spriteSourceSize": {"x":0,"y":0,"w":24,"h":34}, + "sourceSize": {"w":24,"h":34} + }, + {"EOL":"true"} +], +"meta": { + "app": "ShoeBox", + "exporter": "melonJS", + "size": {"w":512,"h":256} +} +} \ No newline at end of file diff --git a/Games/Contra-Shooter/data/img/sprite/bill-lance.png b/Games/Contra-Shooter/data/img/sprite/bill-lance.png new file mode 100644 index 0000000000..7e66088d5e Binary files /dev/null and b/Games/Contra-Shooter/data/img/sprite/bill-lance.png differ diff --git a/Games/Contra-Shooter/data/img/sprite/explosion-sprite.png b/Games/Contra-Shooter/data/img/sprite/explosion-sprite.png new file mode 100644 index 0000000000..118beb66f1 Binary files /dev/null and b/Games/Contra-Shooter/data/img/sprite/explosion-sprite.png differ diff --git a/Games/Contra-Shooter/data/img/sprite/power-ups-sprite.png b/Games/Contra-Shooter/data/img/sprite/power-ups-sprite.png new file mode 100644 index 0000000000..fd417d8bae Binary files /dev/null and b/Games/Contra-Shooter/data/img/sprite/power-ups-sprite.png differ diff --git a/Games/Contra-Shooter/data/img/sprite/rock_platform-sprite.png b/Games/Contra-Shooter/data/img/sprite/rock_platform-sprite.png new file mode 100644 index 0000000000..c66433d45f Binary files /dev/null and b/Games/Contra-Shooter/data/img/sprite/rock_platform-sprite.png differ diff --git a/Games/Contra-Shooter/data/map/contra-lvl-0.tmx b/Games/Contra-Shooter/data/map/contra-lvl-0.tmx new file mode 100644 index 0000000000..f9bea17a53 --- /dev/null +++ b/Games/Contra-Shooter/data/map/contra-lvl-0.tmx @@ -0,0 +1,66 @@ + + + + + + + + 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cQEAAHIBAABzAQAAdAEAAHUBAAA9AAAAPQAAAD0AAAA9AAAA1AAAANQAAADUAAAA1AAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAANQAAADyAAAAPQAAAAAAAAAAAAAAuAAAALkAAAC6AAAAAAAAABYCAAAAAAAAAQAAAAAAAAAAAAAAUgEAAFMBAABUAQAAVwEAAAAAAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAHwAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAB8AAADzAQAA9AEAAPYBAAD2AQAA9gEAAPYBAAD2AQAAMgIAAB8AAAAAAAAAAAAAAHABAABxAQAAcgEAAHUBAAAAAAAAUgEAAFMBAABUAQAAVQEAAFYBAABXAQAAAAAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAAAAAAAAAAACOAQAAjwEAAJABAACTAQAAAAAAAHABAABxAQAAcgEAAHMBAAB0AQAAdQEAAAAAAAA9AAAAPQAAAB8AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOAQAAjwEAAJABAACRAQAAkgEAAJMBAAAAAAAAHwAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAHwAAAB8AAAAfAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFIBAABTAQAAVAEAAFUBAABWAQAAVwEAAAAAAAAAAAAAAAAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAB8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAQAAcQEAAHIBAABzAQAAdAEAAHUBAAAAAAAAAAAAAAAAAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAD0AAAA9AAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAAAAAABSAQAAVgEAAFcBAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAAAAAAAAAAAAAAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAcAEAAHQBAAB1AQAAAAAAAAAAAAAAAAAAAAAAAFIBAABTAQAAVAEAAFUBAABWAQAAVwEAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI4BAAA9AAAAHwAAAAAAAACSAQAAkwEAAAAAAAAAAAAAAAAAAAAAAABwAQAAcQEAAHIBAABzAQAAdAEAAHUBAAA9AAAAHwAAAB8AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAADzAQAA9AEAAPYBAAD2AQAA9gEAAPYBAAD2AQAA9gEAADICAAAfAAAAygEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAA9AAAAPQAAAD0AAAA9AAAAEQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAPQAAAOgBAADpAQAA6gEAAOsBAADsAQAA7QEAAO4BAADsAQAA7AEAAOwBAADsAQAA7AEAAO8BAAAfAAAAHwAAAB8AAAAfAAAAIQAAACEAAAAhAAAAHwAAAB8AAAAfAAAAHwAAACEAAAAhAAAAIQAAACEAAAAfAAAAHwAAAB8AAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAPQAAAD0AAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADFAQAA + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAhwEAAF8AAACHAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAAAAAAM4BAACuAQAAzQEAAK0BAACtAQAAgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArgEAAM0BAACDAAAAXwAAAF8AAACBAAAAgwAAAAAAAADNAQAAgQAAAMwBAACtAQAArQEAAK0BAABfAAAAggAAAIcBAADPAQAAsQEAAM4BAACxAQAAgwAAAM4BAADPAQAAXwAAAIIAAADLAQAAAAAAAAAAAADOAQAAsQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNAQAArQEAAAAAAAAAAAAAAAAAAK8BAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLAQAAzwEAALEBAACBAAAArQEAAMsBAADMAQAAzwEAAMwBAADPAQAAzAEAAIcBAAAAAAAAAAAAAK4BAAAAAAAAAAAAAAAAAACxAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8AAACxAQAAAAAAAAAAAACvAQAAAAAAAAAAAAAAAAAArwEAAIcBAADOAQAAXgAAAAAAAABeAAAAAAAAAAAAAADMAQAAXwAAAIcBAADLAQAAzgEAAMsBAADOAQAAAAAAAAAAAAAAAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAK8BAADPAQAAzQEAAMsBAABfAAAAzAEAAAAAAAAAAAAAAAAAAAAAAACvAQAArwEAAMsBAACuAQAAzgEAALABAADOAQAAhwEAAMsBAACHAQAArgEAAMwBAADMAQAArQEAAK0BAACtAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAABfAAAAAAAAAAAAAACDAAAAgQAAAIIAAADNAQAAgQAAAK0BAACwAQAAzwEAAM0BAABfAAAArwEAAM0BAADPAQAAgQAAAF8AAACtAQAAzQEAAAAAAADMAQAAAAAAAAAAAACvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArwEAAF8AAACxAQAAsQEAAIcBAAAAAAAAAAAAAAAAAAAAAAAAXwAAAIEAAADLAQAAywEAAIEAAACBAAAAggAAAF4AAAAAAAAAzwEAAIMAAAAAAAAAgwAAAAAAAADOAQAAzgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK8BAAAAAAAArwEAAK0BAABfAAAAgwAAAMsBAACuAQAArQEAAAAAAAAAAAAAzwEAAAAAAABfAAAAXwAAAK4BAACxAQAAAAAAAM4BAACDAAAArQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCAAAAgQAAAAAAAACBAAAArwEAAK4BAADLAQAAsQEAAIcBAADLAQAAzAEAAMwBAACwAQAAywEAAF4AAACHAQAAywEAAM8BAADMAQAAhwEAAAAAAACvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK4BAACtAQAAgwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQEAAIEAAADOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADPAQAArgEAAM0BAACDAAAAXgAAAIcBAADLAQAAgQAAAK0BAACDAAAAggAAAAAAAAAAAAAAXwAAAM8BAABeAAAAsAEAAIEAAACuAQAArgEAAF8AAACuAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzQEAAMsBAACDAAAAsAEAAMwBAAAAAAAAAAAAAM0BAADLAQAAsQEAAMsBAAAAAAAAAAAAAM0BAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAggAAAIEAAAAAAAAAAAAAAAAAAACxAQAAsAEAAK8BAAAAAAAAAAAAAK0BAADOAQAAAAAAAAAAAADPAQAAzAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK0BAABfAAAAAAAAAF4AAADOAQAAzwEAAIIAAADLAQAAAAAAAAAAAACCAAAArwEAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQEAAF8AAABfAAAAXgAAALABAACwAQAAzwEAAAAAAACHAQAAggAAAIMAAADOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEAAACHAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwBAACuAQAAggAAAM4BAAAAAAAAsAEAAAAAAAAAAAAAgwAAAK0BAABfAAAAXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMAQAArQEAAAAAAACBAAAAsAEAAIIAAADLAQAAAAAAALABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArwEAAIIAAACuAQAArgEAAF8AAACxAQAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAzwEAAM8BAACvAQAAzAEAAMsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNAQAAggAAAF4AAACwAQAAXgAAAIMAAACDAAAAAAAAAAAAAADOAQAAsQEAAM0BAACtAQAAgQAAAK8BAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwAAAMwBAABeAAAArQEAAK0BAACtAQAAzAEAAF8AAACBAAAAzgEAAK0BAADLAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Games/Contra-Shooter/data/map/contra-lvl-1.tmx b/Games/Contra-Shooter/data/map/contra-lvl-1.tmx new file mode 100644 index 0000000000..6aaae7095c --- /dev/null +++ b/Games/Contra-Shooter/data/map/contra-lvl-1.tmx @@ -0,0 +1,68 @@ + + + + + + + + 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cQEAAHIBAABzAQAAdAEAAHUBAAA9AAAAPQAAAD0AAAA9AAAA1AAAANQAAADUAAAA1AAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAANQAAADyAAAAHwAAAAAAAAAAAAAAuAAAALkAAAC6AAAAAAAAABYCAAAAAAAAAQAAAAAAAAAAAAAAUgEAAFMBAABUAQAAVwEAAAAAAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAHwAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAB8AAADzAQAA9AEAAPYBAAD2AQAA9gEAAPYBAAD2AQAAMgIAAB8AAAAAAAAAAAAAAHABAABxAQAAcgEAAHUBAAAAAAAAUgEAAFMBAABUAQAAVQEAAFYBAABXAQAAAAAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAAAAAAAAAAACOAQAAjwEAAJABAACTAQAAAAAAAHABAABxAQAAcgEAAHMBAAB0AQAAdQEAAAAAAAA9AAAAPQAAAB8AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOAQAAjwEAAJABAACRAQAAkgEAAJMBAAAAAAAAHwAAAPIAAADyAAAA8gAAAPIAAADzAAAAHwAAAB8AAAAfAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFIBAABTAQAAVAEAAFUBAABWAQAAVwEAAAAAAAAAAAAAAAAAAD0AAADyAAAA8gAAAPMAAADzAAAA8wAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAAfAAAAHwAAAB8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAQAAcQEAAHIBAABzAQAAdAEAAHUBAAAAAAAAAAAAAAAAAAA9AAAA8gAAAPMAAADyAAAA8wAAAPMAAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAD0AAAA9AAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAAAAAABSAQAAVgEAAFcBAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAAAAAAAAAAAAAAAAAHwAAAPQBAAD1AQAA9QEAAPUBAAAyAgAAHwAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAcAEAAHQBAAB1AQAAAAAAAAAAAAAAAAAAAAAAAFIBAABTAQAAVAEAAFUBAABWAQAAVwEAAD0AAADyAAAA8wAAAPMAAADzAAAA8wAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI4BAAA9AAAAHwAAAAAAAACSAQAAkwEAAAAAAAAAAAAAAAAAAAAAAABwAQAAcQEAAHIBAABzAQAAdAEAAHUBAAAfAAAA8gAAAPIAAADzAAAA8gAAAPMAAAA9AAAAHwAAAB8AAAAfAAAAHwAAAD0AAAA9AAAAPQAAAB8AAAAfAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPQAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjgEAAI8BAACQAQAAkQEAAJIBAACTAQAAPQAAAPIAAADyAAAA8gAAAPIAAADzAAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAD0AAADzAQAA9AEAAPYBAAD2AQAA9gEAAPYBAAD2AQAA9gEAADICAAAfAAAAygEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAADyAAAA8wAAAPMAAADzAAAA8wAAAD0AAAA9AAAAPQAAAD0AAAA9AAAAPQAAAB8AAAA9AAAAPQAAAD0AAAA9AAAAEQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAAPQAAAOgBAADpAQAA6gEAAOsBAADsAQAA7QEAAO4BAADsAQAA7AEAAOwBAADsAQAA7AEAAO8BAAAfAAAAHwAAAB8AAAAfAAAAIQAAACEAAAAhAAAAHwAAAB8AAAAfAAAAHwAAACEAAAAhAAAAIQAAACEAAAAfAAAAHwAAAB8AAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAPQAAAD0AAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADEAQAAxAEAAMQBAADFAQAA + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAhwEAAF8AAACHAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAAAAAAM4BAACuAQAAzQEAAK0BAACtAQAAgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArgEAAM0BAACDAAAAXwAAAF8AAACBAAAAgwAAAAAAAADNAQAAgQAAAMwBAACtAQAArQEAAK0BAABfAAAAggAAAIcBAADPAQAAsQEAAM4BAACxAQAAgwAAAM4BAADPAQAAXwAAAIIAAADLAQAAAAAAAAAAAADOAQAAsQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNAQAArQEAAAAAAAAAAAAAAAAAAK8BAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLAQAAzwEAALEBAACBAAAArQEAAMsBAADMAQAAzwEAAMwBAADPAQAAzAEAAIcBAAAAAAAAAAAAAK4BAAAAAAAAAAAAAAAAAACxAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8AAACxAQAAAAAAAAAAAACvAQAAAAAAAAAAAAAAAAAArwEAAIcBAADOAQAAXgAAAAAAAABeAAAAAAAAAAAAAADMAQAAXwAAAIcBAADLAQAAzgEAAMsBAADOAQAAAAAAAAAAAAAAAAAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAK8BAADPAQAAzQEAAMsBAABfAAAAzAEAAAAAAAAAAAAAAAAAAAAAAACvAQAArwEAAMsBAACuAQAAzgEAALABAADOAQAAhwEAAMsBAACHAQAArgEAAMwBAADMAQAArQEAAK0BAACtAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAABfAAAAAAAAAAAAAACDAAAAgQAAAIIAAADNAQAAgQAAAK0BAACwAQAAzwEAAM0BAABfAAAArwEAAM0BAADPAQAAgQAAAF8AAACtAQAAzQEAAAAAAADMAQAAAAAAAAAAAACvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArwEAAF8AAACxAQAAsQEAAIcBAAAAAAAAAAAAAAAAAAAAAAAAXwAAAIEAAADLAQAAywEAAIEAAACBAAAAggAAAF4AAAAAAAAAzwEAAIMAAAAAAAAAgwAAAAAAAADOAQAAzgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK8BAAAAAAAArwEAAK0BAABfAAAAgwAAAMsBAACuAQAArQEAAAAAAAAAAAAAzwEAAAAAAABfAAAAXwAAAK4BAACxAQAAAAAAAM4BAACDAAAArQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCAAAAgQAAAAAAAACBAAAArwEAAK4BAADLAQAAsQEAAIcBAADLAQAAzAEAAMwBAACwAQAAywEAAF4AAACHAQAAywEAAM8BAADMAQAAhwEAAAAAAACvAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK4BAACtAQAAgwAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQEAAIEAAADOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADPAQAArgEAAM0BAACDAAAAXgAAAIcBAADLAQAAgQAAAK0BAACDAAAAggAAAAAAAAAAAAAAXwAAAM8BAABeAAAAsAEAAIEAAACuAQAArgEAAF8AAACuAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzQEAAMsBAACDAAAAsAEAAMwBAAAAAAAAAAAAAM0BAADLAQAAsQEAAMsBAAAAAAAAAAAAAM0BAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAggAAAIEAAAAAAAAAAAAAAAAAAACxAQAAsAEAAK8BAAAAAAAAAAAAAK0BAADOAQAAAAAAAAAAAADPAQAAzAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK0BAABfAAAAAAAAAF4AAADOAQAAzwEAAIIAAADLAQAAAAAAAAAAAACCAAAArwEAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQEAAF8AAABfAAAAXgAAALABAACwAQAAzwEAAAAAAACHAQAAggAAAIMAAADOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIEAAACHAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwBAACuAQAAggAAAM4BAAAAAAAAsAEAAAAAAAAAAAAAgwAAAK0BAABfAAAAXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMAQAArQEAAAAAAACBAAAAsAEAAIIAAADLAQAAAAAAALABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArwEAAIIAAACuAQAArgEAAF8AAACxAQAAXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMsBAAAAAAAAzwEAAM8BAACvAQAAzAEAAMsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNAQAAggAAAF4AAACwAQAAXgAAAIMAAACDAAAAAAAAAAAAAADOAQAAsQEAAM0BAACtAQAAgQAAAK8BAABfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwAAAMwBAABeAAAArQEAAK0BAACtAQAAzAEAAF8AAACBAAAAzgEAAK0BAADLAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Games/Contra-Shooter/data/map/contra-tileset-1.tmx b/Games/Contra-Shooter/data/map/contra-tileset-1.tmx new file mode 100644 index 0000000000..2d41291607 --- /dev/null +++ b/Games/Contra-Shooter/data/map/contra-tileset-1.tmx @@ -0,0 +1,25 @@ + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAAAAAABAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAAAAAALAAAACwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAAAAAALAAAACwAAAAsAAAAAAAAAAAAAAAEAAAALAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAEAAAABAAAAAQAAAAAAAAALAAAACwAAAAEAAAAAAAAAAQAAAAEAAAALAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAAAAAALAAAACwAAAAsAAAABAAAACwAAAAsAAAALAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAEAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + + + + + + + + + + + + + + + diff --git a/Games/Contra-Shooter/data/map/contra-tileset-2.tmx b/Games/Contra-Shooter/data/map/contra-tileset-2.tmx new file mode 100644 index 0000000000..202312e61b --- /dev/null +++ b/Games/Contra-Shooter/data/map/contra-tileset-2.tmx @@ -0,0 +1,34 @@ + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAAAAAABAAAACwAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAALAAAAAAAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAAAAAALAAAACwAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAsAAAALAAAAAAAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAAAAAALAAAACwAAAAsAAAAAAAAAAAAAAAEAAAALAAAACwAAAAsAAAALAAAAAAAAAAsAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAEAAAABAAAAAQAAAAAAAAALAAAACwAAAAEAAAAAAAAAAQAAAAEAAAALAAAACwAAAAsAAAALAAAAAQAAAAsAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAAAAAALAAAACwAAAAsAAAABAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAEAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Games/Contra-Shooter/data/sfx/cling.mp3 b/Games/Contra-Shooter/data/sfx/cling.mp3 new file mode 100644 index 0000000000..a879cf68e0 Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/cling.mp3 differ diff --git a/Games/Contra-Shooter/data/sfx/cling.ogg b/Games/Contra-Shooter/data/sfx/cling.ogg new file mode 100644 index 0000000000..c345a4c0a7 Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/cling.ogg differ diff --git a/Games/Contra-Shooter/data/sfx/jump.mp3 b/Games/Contra-Shooter/data/sfx/jump.mp3 new file mode 100644 index 0000000000..1e8aebff88 Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/jump.mp3 differ diff --git a/Games/Contra-Shooter/data/sfx/jump.ogg b/Games/Contra-Shooter/data/sfx/jump.ogg new file mode 100644 index 0000000000..a4cf2c1b1f Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/jump.ogg differ diff --git a/Games/Contra-Shooter/data/sfx/stomp.mp3 b/Games/Contra-Shooter/data/sfx/stomp.mp3 new file mode 100644 index 0000000000..3e6d0a2a24 Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/stomp.mp3 differ diff --git a/Games/Contra-Shooter/data/sfx/stomp.ogg b/Games/Contra-Shooter/data/sfx/stomp.ogg new file mode 100644 index 0000000000..0fdd4c3edd Binary files /dev/null and b/Games/Contra-Shooter/data/sfx/stomp.ogg differ diff --git a/Games/Contra-Shooter/icons/touch-icon-ipad-76x76.png b/Games/Contra-Shooter/icons/touch-icon-ipad-76x76.png new file mode 100644 index 0000000000..142f22f241 Binary files /dev/null and b/Games/Contra-Shooter/icons/touch-icon-ipad-76x76.png differ diff --git a/Games/Contra-Shooter/icons/touch-icon-ipad-retina-152x152.png b/Games/Contra-Shooter/icons/touch-icon-ipad-retina-152x152.png new file mode 100644 index 0000000000..960fae6c33 Binary files /dev/null and b/Games/Contra-Shooter/icons/touch-icon-ipad-retina-152x152.png differ diff --git a/Games/Contra-Shooter/icons/touch-icon-iphone-60x60.png b/Games/Contra-Shooter/icons/touch-icon-iphone-60x60.png new file mode 100644 index 0000000000..9453e148ed Binary files /dev/null and b/Games/Contra-Shooter/icons/touch-icon-iphone-60x60.png differ diff --git a/Games/Contra-Shooter/icons/touch-icon-iphone-retina-120x120.png b/Games/Contra-Shooter/icons/touch-icon-iphone-retina-120x120.png new file mode 100644 index 0000000000..9c27b0856c Binary files /dev/null and b/Games/Contra-Shooter/icons/touch-icon-iphone-retina-120x120.png differ diff --git a/Games/Contra-Shooter/icons/touch-icon-marketplace-128x128.png b/Games/Contra-Shooter/icons/touch-icon-marketplace-128x128.png new file mode 100644 index 0000000000..e6796420d7 Binary files /dev/null and b/Games/Contra-Shooter/icons/touch-icon-marketplace-128x128.png differ diff --git a/Games/Contra-Shooter/index.css b/Games/Contra-Shooter/index.css new file mode 100644 index 0000000000..6ffeae8b90 --- /dev/null +++ b/Games/Contra-Shooter/index.css @@ -0,0 +1,53 @@ +body { + background-color: #4d4d4d; + color: #fff; + + /* Allow mouse dragging. */ + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + -webkit-user-select: none; + user-select: none; + + /* disable touch panning/zooming */ + -ms-touch-action: none; + touch-action: none; + + /* Allow canvas to hit the edges of the browser viewport. */ + margin: 0; +} + +#screen canvas { + margin: auto; + display: block; + + /* disable scaling interpolation */ + image-rendering: optimizeSpeed; + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: optimize-contrast; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} + +.html-space { + + position: absolute; + margin: auto; + z-index: 2; + transform: translate(50%, -50%); + color: white; +} + +.html-flex{ + display: flex; + justify-content: center; + align-items: center; +} + +.html-input { + + display: block; + z-index: 2; +} \ No newline at end of file diff --git a/Games/Contra-Shooter/index.html b/Games/Contra-Shooter/index.html new file mode 100644 index 0000000000..64b18b123a --- /dev/null +++ b/Games/Contra-Shooter/index.html @@ -0,0 +1,80 @@ + + + + Contra 2000 + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Games/Contra-Shooter/js/commons/commons-calc.js b/Games/Contra-Shooter/js/commons/commons-calc.js new file mode 100644 index 0000000000..6addfb11f5 --- /dev/null +++ b/Games/Contra-Shooter/js/commons/commons-calc.js @@ -0,0 +1,44 @@ +var commons = commons || {}; + +commons.calc = { + + randomInt : function (min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + dxToThing : function (from, target){ + return target.pos.x - from.pos.x; + }, + + dyToThing : function (from, target){ + return from.pos.y - target.pos.y; + }, + + angleToThing : function(from, target) { + var dy = this.dyToThing(from, target); + var dx = this.dxToThing(from, target); + return Math.atan2(dy, dx); + }, + + upOrDownToThing: function(from, target) { + + const angle = this.angleToThing(from, target); + + //angle will be between PI and -PI + if(angle < -Math.PI * 5/6){ + return 1; + } + else if(angle < -Math.PI * 1/6){ + return -1; + } + else if(angle < Math.PI * 1/6){ + return 1; + } + else if(angle < Math.PI * 5/6){ + return -1 + } + return 0; + } +}; diff --git a/Games/Contra-Shooter/js/commons/commons-flag-collection.js b/Games/Contra-Shooter/js/commons/commons-flag-collection.js new file mode 100644 index 0000000000..65ef89a563 --- /dev/null +++ b/Games/Contra-Shooter/js/commons/commons-flag-collection.js @@ -0,0 +1,18 @@ +function Flags(){ + + /*-------------------------------- + * Vars + *--------------------------------*/ + this.flags = {}; + + + /*-------------------------------- + * Get / Set + *--------------------------------*/ + this.setFlag = function(name, val){ + this.flags[name] = val; + }; + this.getFlag = function(name){ + return this.flags[name]; ; + }; +} diff --git a/Games/Contra-Shooter/js/commons/commons-net.js b/Games/Contra-Shooter/js/commons/commons-net.js new file mode 100644 index 0000000000..18832ee4ce --- /dev/null +++ b/Games/Contra-Shooter/js/commons/commons-net.js @@ -0,0 +1,51 @@ + +commons = commons || {}; +commons.net = {}; + +commons.net.Http = function(url, options){ + + options = options || {}; + this.url = url || {}; + + /* --------------------------------------- + * Get -> Returns Observable + * ---------------------------------------*/ + this.getJSON = function(endpoint) { + + var requestUrl = this.url + endpoint; + return Rx.DOM.getJSON(requestUrl); + } + + /* --------------------------------------- + * Event Stream -> Returns continuous HTTP event stream + * ---------------------------------------*/ + this.eventstream = function(endpoint, handler){ + + this.es = new EventSource(this.url + endpoint); + var esrxjs = new Rx.Observable((observer) => {this.es.addEventListener('message', (e) => observer.next(e));}); + + esrxjs.subscribe(handler); + }; +}; + + +commons.net.Socket = function(socket){ + + this.ws = new WebSocket(socket); + this.wso = new Rx.Observable((observer) => {this.ws.addEventListener('message', (e) => observer.next(e));}); + + this.subscribe = function(f){ + if (this.socketReady()) this.wso.subscribe(f); + }; + this.send = function(msg){ + if (this.socketReady()) this.ws.send(msg); + }; + + this.socketReady = function() { + return (this.ws != null && this.ws.readyState == 1); + }; + this.destroy = function() { + if(this.socketReady()) this.ws.close(); + }; + +} \ No newline at end of file diff --git a/Games/Contra-Shooter/js/commons/commons-utils.js b/Games/Contra-Shooter/js/commons/commons-utils.js new file mode 100644 index 0000000000..d687bf731a --- /dev/null +++ b/Games/Contra-Shooter/js/commons/commons-utils.js @@ -0,0 +1,58 @@ +var commons = commons || {}; + +commons.game = { + + teams : { + "B": {tid: "B", name: "Red", color: "#0000FF"}, + "R": {tid: "R", name: "Red", color: "#FF0000"}, + "G": {tid: "G", name: "Green", color: "#00FF00"}, + "Y": {tid: "Y", name: "Yellow", color: "#00FFFF"}, + "U": {tid: "U", name: "Unknown", color: "#FFFFFF"}, + }, + + getColorForTeam: function(tid){ + + var team = this.teams[tid] || commons.game.teams["U"]; + return team.color; + }, + effect_ExplodeOnTarget: function (target) { + + var explosion = me.pool.pull("explosion-1", target.pos.x, target.pos.y); + me.game.world.addChild(explosion, 99); + }, +}; + +commons.me = { + + textureSpriteHelper : function(texture, imgPrefix, mappings){ + + var fileNames = []; + var animations = {}; + + mappings.forEach(statemap => { + + var name = statemap["name"]; + var frames = statemap["frames"]; + var delay = statemap["delay"] || 200; + + animations[name] = []; + + frames.forEach(frameIdx => { + + var fileName = imgPrefix + ( frameIdx < 10 ? "_0" : "_" ); + fileName += frameIdx.toString() + ".png"; + + fileNames.push(fileName); + animations[name].push({name: fileName, delay: delay}); + }); + }); + + + var sprite = texture.createAnimationFromName(fileNames); + + for(var animState in animations){ + sprite.addAnimation(animState, animations[animState]); + } + return sprite; + } +}; diff --git a/Games/Contra-Shooter/js/display-effects/debug-highlights.js b/Games/Contra-Shooter/js/display-effects/debug-highlights.js new file mode 100644 index 0000000000..c15cd224d4 --- /dev/null +++ b/Games/Contra-Shooter/js/display-effects/debug-highlights.js @@ -0,0 +1,99 @@ + + +var HL_PATHGRAPH = me.Renderable.extend({ + + init : function (){ + this._super(me.Renderable, "init", [0, 0, 500, 600]); + }, + + show : function (pathGraph) { + + this.pathGraph = pathGraph; + + if (!this.visible) { + me.game.world.addChild(this, 4); + this.visible = true; + me.game.repaint(); + } + }, + hide : function () { + + this.pathGraph = null; + + if (this.visible) { + me.game.world.removeChild(this, true); + this.visible = false; + me.game.repaint(); + } + }, + destroy : function () {}, + + draw : function (renderer) { + + var color = renderer.getColor(); + renderer.setColor("#FFFF00"); + + for(var surfkey in this.pathGraph.surfaces){ + this.pathGraph.surfaces[surfkey].connections.forEach(con => { + renderer.strokeLine(con.fromX, con.fromY, con.toX, con.toY); + }); + } + renderer.setColor(color); + }, + + update : function (time) { + return this.visible; + } +}); + +var HL_PlayerStateInfo = me.Renderable.extend({ + + init : function (target){ + + this.target = target; + + this._super(me.Renderable, "init", [target.pos.x, target.pos.y, target.width, target.height]); + this.alwaysUpdate = true; + }, + + show : function () { + if (!this.visible) { + me.game.world.addChild(this, 4); + this.visible = true; + me.game.repaint(); + } + }, + hide : function () { + if (this.visible) { + me.game.world.removeChild(this, true); + this.visible = false; + me.game.repaint(); + } + }, + destroy : function () {}, + + draw : function (renderer) { + + var color = renderer.getColor(); + + renderer.setGlobalAlpha(.4); + renderer.setColor('#00FF00'); + renderer.setGlobalAlpha(1); + renderer.strokeRect(this.target.pos.x, this.target.pos.y, this.target.width, this.target.height); + + var fireValues = this.target.playerState.calcBarrelPosition() + renderer.setColor('#FF0000'); + renderer.fillArc(fireValues[0]-2, fireValues[1]-2, 4, 0, 6.29); + + var center = this.target.playerState.calcCenterPosition(); + renderer.setColor('#0000FF'); + renderer.fillArc(center[0]-2, center[1]-2, 4, 0, 6.29); + + renderer.setGlobalAlpha(1); + renderer.setColor(color); + }, + + update : function (time) { + return this.visible; + } +}); \ No newline at end of file diff --git a/Games/Contra-Shooter/js/display-effects/html-space.js b/Games/Contra-Shooter/js/display-effects/html-space.js new file mode 100644 index 0000000000..5db124561e --- /dev/null +++ b/Games/Contra-Shooter/js/display-effects/html-space.js @@ -0,0 +1,46 @@ +/* types + + 1 -> full + 2 -> top half + 3 -> middle half + 4 -> bottom half +*/ + +game.HtmlSpace = me.Renderable.extend({ + + init : function (type, options) { + + type = type || 1; + options = options || {}; + var background = options.background || null; + var ht = options.height || '50%'; + + switch (type){ + + case 1 : + this.cssVals = {height: "100%"}; + break; + + case 2 : + this.cssVals = {top: "25%", height: "50%"}; + break; + + case 3 : + this.cssVals = {top: "50%", height: "50%"}; + break; + + case 4 : + this.cssVals = {top: "75%", height: "50%"}; + break; + } + if(background) this.cssVals["background"] = background; + this.cssVals["width"] = "50%"; + + this.$htmlspace = $('
').css(this.cssVals); + $(me.video.getWrapper()).append(this.$htmlspace); + }, + + destroy : function () { + this.$htmlspace.remove(); + }, +}); \ No newline at end of file diff --git a/Games/Contra-Shooter/js/display-effects/html-textinput.js b/Games/Contra-Shooter/js/display-effects/html-textinput.js new file mode 100644 index 0000000000..ff91838fc5 --- /dev/null +++ b/Games/Contra-Shooter/js/display-effects/html-textinput.js @@ -0,0 +1,50 @@ +game.HtmlHeader3 = me.Renderable.extend({ + + init : function($parent, header){ + + this.$div = $('
'); + this.$label = $('

' + header + '

'); + + $parent.append(this.$div); + this.$div.append(this.$label); + }, + destroy : function () { + this.$div.remove(); + this.$label.remove(); + }, +}) +game.HtmlTextInput = me.Renderable.extend({ + + init : function ($parent, label, type, length) { + + this.$div = $('
'); + this.$label = $('' + label + '').css({"padding-right": "10px"}); + this.$input = $(''); + + switch (type) { + case "text": + this.$input + .attr("maxlength", length) + .attr("pattern", "[a-zA-Z0-9_\-]+"); + break; + case "number": + this.$input.attr("max", length); + break; + } + + $parent.append(this.$div); + this.$div.append(this.$label); + this.$div.append(this.$input); + this.$input.focus(); + }, + + destroy : function () { + this.$input.remove(); + this.$label.remove(); + this.$div.remove(); + }, + + getInputValue : function () { + return this.$input.val(); + } +}); \ No newline at end of file diff --git a/Games/Contra-Shooter/js/display-effects/player-highlights.js b/Games/Contra-Shooter/js/display-effects/player-highlights.js new file mode 100644 index 0000000000..f6457ffe43 --- /dev/null +++ b/Games/Contra-Shooter/js/display-effects/player-highlights.js @@ -0,0 +1,59 @@ + + +var HL_PlayerWithReload = me.Renderable.extend({ + + init : function (target){ + + this.target = target; + this.teamColor = commons.game.getColorForTeam(target.team); + this._super(me.Renderable, "init", [target.pos.x, target.pos.y, target.width, target.height]); + }, + + show : function () { + if (!this.visible) { + me.game.world.addChild(this, 4); + this.visible = true; + me.game.repaint(); + } + }, + hide : function () { + if (this.visible) { + me.game.world.removeChild(this, true); + this.visible = false; + me.game.repaint(); + } + }, + destroy : function () {}, + + draw : function (renderer) { + + //step 1.. + //figure out draw vars + const reloadPerc = commons.playerUtils.getGunReloadPercent(this.target) || 0; + const arcRad = 6.28 * reloadPerc; + + const center = commons.playerUtils.getCenterPosition(this.target) || [0,0]; + const posX = this.target.pos.x; + const posY = center[1] - .5 * this.target.height; + + const color = renderer.getColor(); + + + //step 2... + //do circle render + renderer.setGlobalAlpha(.4); + renderer.setColor(this.teamColor); + renderer.fillArc(posX, posY, this.target.width/2, 0, 6.28); + + renderer.setGlobalAlpha(1); + renderer.setColor('#FFFFFF'); + renderer.strokeArc(posX, posY, this.target.width/2, 0, arcRad); + + renderer.setGlobalAlpha(1); + renderer.setColor(color); + }, + + update : function (time) { + return this.visible; + } +}); diff --git a/Games/Contra-Shooter/js/display-effects/view-zoom.js b/Games/Contra-Shooter/js/display-effects/view-zoom.js new file mode 100644 index 0000000000..ed65a1a30e --- /dev/null +++ b/Games/Contra-Shooter/js/display-effects/view-zoom.js @@ -0,0 +1,65 @@ +function Zoomer(inkey, outkey, delta){ + + /* ------------------------------------------- + * Set up / enable / disable + *-------------------------------------------*/ + this.inkey = inkey || me.input.KEY.Z; + this.outkey = outkey || me.input.KEY.X; + + this.delta = delta || .02; + + + this.enable = function() { + + if(this.listener) return; + + me.input.bindKey(this.inkey, "zoomer-in", true); + me.input.bindKey(this.outkey, "zoomer-out", true); + me.input.bindKey(me.input.KEY.Q, "zoomer-reset", true); + + var that = this; + + this.listener = me.event.subscribe(me.event.KEYDOWN, + (action, keycode, edge) => { + if (action === "zoomer-in") this.doViewportZoom(1 + this.delta); + if (action === "zoomer-out")this.doViewportZoom(1 - this.delta); + if (action === "zoomer-reset")this.doZoomReset(); + } + ); + }; + this.disable = function(){ + + if(!this.listener) return; + + me.input.unbindKey(this.inkey); + me.input.unbindKey(this.outkey); + me.event.unsubscribe(this.listener); + this.listener = null; + }; + + + /* ------------------------------------------- + * Zoom Functions + *-------------------------------------------*/ + this.doViewportZoom = function(delta){ + + var viewport = me.game.viewport; + viewport.currentTransform.translate( + viewport.width * viewport.anchorPoint.x, + viewport.height * viewport.anchorPoint.y + ); + + viewport.currentTransform.scale(delta); + + viewport.currentTransform.translate( + -viewport.width * viewport.anchorPoint.x, + -viewport.height * viewport.anchorPoint.y + ); + + me.game.viewport.follow(game.manager.gameState.fpu, me.game.viewport.AXIS.BOTH); + }; + this.doZoomReset = function(){ + + me.game.viewport.currentTransform.identity(); + } +} \ No newline at end of file diff --git a/Games/Contra-Shooter/js/entities/HUD.js b/Games/Contra-Shooter/js/entities/HUD.js new file mode 100644 index 0000000000..2e681ee964 --- /dev/null +++ b/Games/Contra-Shooter/js/entities/HUD.js @@ -0,0 +1,67 @@ +game.HUD = game.HUD || {}; + + +game.HUD.Container = me.Container.extend({ + + init: function() { + this._super(me.Container, 'init'); + this.isPersistent = false; + this.floating = true; + this.name = "HUD"; + + this.addChild(new game.HUD.fpuInfo(25, 25)); + } +}); + + +game.HUD.fpuInfo = me.Renderable.extend({ + + init: function(x, y) { + this.badge_icon = me.loader.getImage('icon_badge_24_48'); + this._super(me.Renderable, 'init', [x, y, 185, 130]); + this.lives = 3; + this.score = 0; + }, + + update : function () { + + var fpu = game.manager.gameState.fpu || null; + + if(fpu == null){ + return false; + } + + if (this.lives !== fpu.lives || this.score !== fpu.score) { + this.lives = fpu.lives; + this.score = fpu.score; + return true; + } + return false; + }, + + + draw : function (renderer) { + + var color = renderer.getColor(); + + renderer.setColor('#000000'); + renderer.setGlobalAlpha(.85); + renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height); + + renderer.setGlobalAlpha(1); + renderer.setColor('#FFFFFF'); + renderer.strokeRect(this.pos.x, this.pos.y, this.width, this.height); + + renderer.setColor(color); + + var imgX = this.pos.x + 15; + var imgY = this.pos.y + 11; + for(var i = 0; i me.game.world.removeChild(that)); + }, + + update : function (dt) { + return (this._super(me.Entity, 'update', [dt]) || true); + }, + onCollision : function (response, other) { + return false; + } +}); diff --git a/Games/Contra-Shooter/js/entities/platform.js b/Games/Contra-Shooter/js/entities/platform.js new file mode 100644 index 0000000000..61a770766f --- /dev/null +++ b/Games/Contra-Shooter/js/entities/platform.js @@ -0,0 +1,16 @@ +game.Platform = me.Entity.extend({ + + init : function (x, y, settings) { + + this._super(me.Entity, 'init', [x, y, settings]); + + this.type = "platform"; + this.body.collisionType = me.collision.types.ENEMY_OBJECT; + this.body.collisionMask = me.collision.types.ENEMY_OBJECT; + }, + + onCollision : function (response, other) { + return false; + } +}); + diff --git a/Games/Contra-Shooter/js/entities/powerups.js b/Games/Contra-Shooter/js/entities/powerups.js new file mode 100644 index 0000000000..31134006e2 --- /dev/null +++ b/Games/Contra-Shooter/js/entities/powerups.js @@ -0,0 +1,85 @@ + +//sin wave equation = y(t) = Amplitude * sin(2PI * Frequency * t) +// +//where frequency = how many milliseconds it takes to do a cycle +// amplitude = dy during cycle +game.Powerup = me.Entity.extend({ + + init : function (x, y, config) { + + config = config || {}; + this.t = 0; + this.yprime = y; + + this.dirX = config.dirX || 1; + this.velX = config.velX || 2.2; + this.waveamp = config.waveamp || 70; + this.wavefreq = config.wavefreq || 2000; + this.status = 1; + this.prize = "prize-S"; + + var settings = { + width: 27, + height: 19, + image: 'power-ups-sprite' + }; + this._super(me.Entity, "init", [x, y, settings]); + //this.body.collisionType = me.collision.types.ACTION_OBJECT; + + this.body.vel.x = this.dirX * this.velX; + this.body.vel.y = 0; + this.body.gravity = 0; + + this.renderable.addAnimation('floating', [6, 7, 8], 250); + this.renderable.addAnimation('prize-S', [3], 99999999); + this.renderable.setCurrentAnimation('floating'); + + this.renderable.currentTransform.scale(2); + this.alwaysUpdate = true; + }, + + removeIt : function() { + me.game.world.removeChild(this); + }, + + update : function (dt) { + + if(!this.inViewport){ + this.pos.x = 10; + } + if(this.status == 1) { + if(!this.renderable.isCurrentAnimation('floating')){ + this.renderable.setCurrentAnimation('floating'); + } + + this.t += dt; + const y = this.waveamp * Math.sin(2 * Math.PI * this.t / this.wavefreq) + this.yprime; + this.body.update(); + this.pos.y = y; + } + else if(this.status == 2){ + + if(!this.renderable.isCurrentAnimation(this.prize)){ + this.renderable.setCurrentAnimation(this.prize); + } + this.body.update(); + } + me.collision.check(this); + return true; + }, + + onCollision : function (response, other) { + + if(this.status == 1 && other.type == 'bullet'){ + this.body.gravity = 0.8; + this.body.vel.x = 0; + this.body.vel.y = 0; + this.status = 2; + return false; + } + if(this.status == 2 && other.type == 'platform') { + return true; + } + return false; + } +}); diff --git a/Games/Contra-Shooter/js/game-management/game-manager-base.js b/Games/Contra-Shooter/js/game-management/game-manager-base.js new file mode 100644 index 0000000000..e159a01be3 --- /dev/null +++ b/Games/Contra-Shooter/js/game-management/game-manager-base.js @@ -0,0 +1,32 @@ + + +function GameManager() { + + /* ------------------------------------------- + * Set Up / Start game + *-------------------------------------------*/ + this.gameState = {gameId : 0, fpu : {}, entities : {}}; + + this.startGame = function (options, onStart) { + }; + + /* ------------------------------------------- + * Lifecycle + *-------------------------------------------*/ + this.onUpdate = function(){ + }; + + this.onDestroy = function() { + }; + + + /* ------------------------------------------- + * Post Activity + *-------------------------------------------*/ + this.postPlayerUpdate = function(player){ + }; + this.postBullet = function (playerId, team, fromX, fromY, velX, velY) { + }; + this.postBulletDamage = function (target, bullet) { + }; +}; diff --git a/Games/Contra-Shooter/js/game-management/game-manager-localtest.js b/Games/Contra-Shooter/js/game-management/game-manager-localtest.js new file mode 100644 index 0000000000..0fa8c9ea9f --- /dev/null +++ b/Games/Contra-Shooter/js/game-management/game-manager-localtest.js @@ -0,0 +1,87 @@ +function GameManager_LocalTest(options) { + + this.id = 1000; + this.gameState = {gameId : 0, fpu : {}, entities : {}}; + + /* ------------------------------------------- + * Set Up / Start game + *-------------------------------------------*/ + this.startGame = function (options, onStart, onGameOver) { + + this.onGameOver = onGameOver; + this.gameState.gameId = this.id++; + + var fpu = me.pool.pull("goodguy-bill", 50, 220, {mpid: this.id++, team: "B", character : game.constants.Character_Lance}); + me.game.world.addChild(fpu); + me.game.viewport.follow(fpu, me.game.viewport.AXIS.BOTH); + this.gameState.fpu = fpu; + this.gameState.entities[fpu.mpid] = fpu; + onStart(this.gameState); + + this.spawner = new Random_Spawner(); + me.game.world.addChild(this.spawner); + this.spawner.addBadGuyRed(); + this.spawner.addPowerUp(); + }; + this.buildPathGraph = function(){ + + var options = { + standables : me.game.world.getChildByType(game.Platform), + g : 0.98, + jumpVel : game.constants.Player_veloYmax, + xVel : game.constants.Player_veloXmax + }; + game.pathgraph = new rb.paths.PathGraph(options); + game.pathgraph.buildGraph(); + var pathHL = new HL_PATHGRAPH(); + pathHL.show(game.pathgraph); + }; + + + /* ------------------------------------------- + * Lifecycle + *-------------------------------------------*/ + this.onUpdate = function () { + }; + + this.onDestroy = function () { + if (this.spawner){ + this.spawner.stop(); + me.game.world.removeChild(this.spawner); + } + }; + + + /* ------------------------------------------- + * Post Activity + *-------------------------------------------*/ + this.postBullet = function (player, fromX, fromY, dirX, dirY) { + + var config = { + bid: this.id++, + pid: player.mpid, + team: player.team, + dirX: dirX, + dirY: dirY + }; + + var bullet = me.pool.pull("bullet", fromX, fromY, config); + me.game.world.addChild(bullet, 20); + this.gameState.entities[config.bid] = bullet; + }; + + this.postBulletDamage = function (target, bullet) { + var chartype = target.chartype || "BadGuy"; + + if(chartype == "BadGuy") { + this.gameState.fpu.score++; + this.spawner.heatup(); + bullet.onTargetHit(target); + } + if(chartype == "GoodGuy") { + bullet.onTargetHit(target); + } + } + this.postPlayerUpdate = function (player) { + }; +} diff --git a/Games/Contra-Shooter/js/game-management/game-manager-multiplayer.js b/Games/Contra-Shooter/js/game-management/game-manager-multiplayer.js new file mode 100644 index 0000000000..b835b71bb2 --- /dev/null +++ b/Games/Contra-Shooter/js/game-management/game-manager-multiplayer.js @@ -0,0 +1,255 @@ +function GameManager_Multiplayer(options) { + + /* ------------------------------------------- + * Set Up / Start game + *-------------------------------------------*/ + options = options || {}; + this.url = options.url; + this.playerName = options.playerName; + this.onGameOver = options.onGameOver || (e => console.log("game over")); + + this.http = new commons.net.Http("http://" + this.url + "/"); + this.ws = new commons.net.Socket("ws://" + this.url + "/live"); + + this.gjr = {}; + this.gameState = {gameId : 0, fpu : {}, entities : {}}; + + /* ------------------------------------------- + * Join ... search , hookup , respond + *-------------------------------------------*/ + this.joinGame = function (onJoin) { + + const joinurl = "game/game-join/" + this.playerName; + const source = this.http.getJSON(joinurl); + + const that = this; + source.subscribe( + response => {that.gjr = response; onJoin();}, + error => console.error(error), + () => console.log('done') + ); + }; + this.startGame = function() { + + var mpid = this.gjr.playerId; + var team = this.gjr.team; + var x = parseInt(this.gjr.posX); + var y = parseInt(this.gjr.posY); + var config = {mpid: mpid, team: team}; + + var fpu = me.pool.pull("goodguy-bill", x, y, config); + me.game.world.addChild(fpu); + me.game.viewport.follow(fpu, me.game.viewport.AXIS.BOTH); + + this.gameState.fpu = fpu; + this.gameState.entities[fpu.mpid] = fpu; + + var that = this; + this.http.eventstream("game/game-events", e => that.handleEvents(e)); + this.ws.subscribe(m => that.handleUpdates(m)); + }; + + + + /* ------------------------------------------- + * Post Activity + * + * player update message + * "PU|GID|PID|PN|POSX|POSY|state.lc|direction|not used|state.aim" + * + * player hit and killed + * "PH|GID|PID|BID|POSX|POSY" + * + * new bullet + * "BN|GID|PID|TID|POSX|POSY|DIRX|DIRY" + * -----------------------------------------*/ + this.postPlayerUpdate = function(player) { + const lifecycle = commons.playerUtils.getLifecycleState(player); + const input = commons.playerUtils.getCurrentInput(player); + const aiming = commons.playerUtils.getAimDirection(player); + const direction = player.flags.getFlag('direction') || 1; + + const msg = "PU" + "|" + + this.gameState.gameId + "|" + + player.mpid + "|" + player.pos.x + "|" + player.pos.y + "|" + + lifecycle + "|" + + direction + "|" + + 'input' + "|" + + aiming; + + this.ws.send(msg); + }; + this.postBullet = function (player, fromX, fromY, dirX, dirY) { + + var msg = "BN" + "|" + + this.gameState.gameId + "|" + + player.mpid + "|" + + player.team + "|" + + fromX + "|" + fromY + "|" + + dirX + "|" + dirY; + + this.ws.send(msg); + }; + this.postBulletDamage = function (target, bullet) { + + var msg = "PH" + "|" + + this.gameState.gameId + "|" + + target.mpid + "|" + + bullet.bid + "|" + + target.pos.x + "|" + target.pos.y; + + this.ws.send(msg); + }; + + /*--------------------------------------- + * game state update + * + * will be a single string composed of entity update messages + * message = "GSD~TS~MSG1~MSG2~...." + * + * TS = Timestamp milliseconds number + * + * player update + * message = "KP|PID|TID|POSX|POSY|LIVES|SCORE|DIR|AIM|MOTION" + * + * -----------------------------------------*/ + this.handleUpdates = function(gamestateupdate) { + + var gamestateupdateString = gamestateupdate.data; + var msgs = gamestateupdateString.split("~"); + + for (i = 0; i < msgs.length; i++) { + + var msg = msgs[i]; + var msgArray = msg.split("|"); + var msgKey = msgArray[0]; + + // Route message + switch (msgKey) { + + case "KP": + this.handleUpdates_player(msgArray); + break; + } + } + }; + this.handleUpdates_player = function(msg) { + + var mpid = msg[1]; + var team = msg[2]; + var x = parseInt(msg[3]); + var y = parseInt(msg[4]); + var lives = parseInt(msg[5]); + var score = parseInt(msg[6]); + + if (mpid === this.gameState.fpu.mpid) { + this.gameState.fpu.lives = lives; + this.gameState.fpu.score = score; + return; + } + + var npc = this.gameState.entities[mpid]; + if (npc == null) { + + var config = {mpid: mpid, team: "R"}; + npc = me.pool.pull("goodguy-server-npc", x, y, config); + me.game.world.addChild(npc); + this.gameState.entities[mpid] = npc; + } + npc.applyServerCommands(msg); + }; + + /*--------------------------------------- + * game critical events + * + * will be a single event message + * message = "GSD~TS~MSG1" + * + * TS = Timestamp milliseconds number + * + * player killed + * message = "GCU~TS~KPK|PID|BID|SHOOTER-ID" + * + * player removed + * message = "PR~TS~KPR|PID" + * + * bullet shot + * message = "GCU~TS~KBN|BID|PID|TID|POSX|POSY|DIRX|DIRY" + * -----------------------------------------*/ + this.handleEvents = function(gameEvent) { + + var pieces = gameEvent.data.split("~"); + var event = pieces[2] || "U"; + var eventA = event.split("|"); + var eventKey = eventA[0]; + + // Route message + switch (eventKey) { + case "KPK" : + this.handleEvent_playerKilled(eventA); + break; + case "KBN" : + this.handleEvent_newbullet(eventA); + break; + case "KPR" : + this.handleEvent_playerRemoved(eventA); + break; + } + }; + this.handleEvent_playerRemoved = function(event) { + + const pid = event[1]; + + if(pid == this.gameState.fpu.mpid){ + this.onGameOver(this.gameState); + return; + } + + const npc = this.gameState.entities[pid]; + if(npc) npc.applyServerRemoveCommand(); + } + this.handleEvent_playerKilled = function(event) { + + const pid = event[1]; + const bid = event[2]; + const sid = event[3]; + + const player = this.gameState.entities[pid] + const bullet = this.gameState.entities[bid]; + + if(player != null){ + player.processDeath(bullet); + } + if(bullet != null) bullet.removeIt(); + commons.game.effect_PlayerBulletKill(player, bullet); + }; + + this.handleEvent_newbullet = function(event) { + + var bid = event[1]; + var pid = event[2]; + var team = event[3]; + var fromX = parseInt(event[4]); + var fromY = parseInt(event[5]); + var dirX = parseInt(event[6]); + var dirY = parseInt(event[7]); + + var bullet = this.gameState.entities[bid]; + + if(bullet != null) { + return; + } + + var config = { + bid: bid, + pid: pid, + team: team, + dirX: dirX, + dirY: dirY + }; + + bullet = me.pool.pull("bullet", fromX, fromY, config); + me.game.world.addChild(bullet, 20); + this.gameState.entities[bid] = bullet; + }; +} diff --git a/Games/Contra-Shooter/js/game-management/spawner.js b/Games/Contra-Shooter/js/game-management/spawner.js new file mode 100644 index 0000000000..b77e5f69ec --- /dev/null +++ b/Games/Contra-Shooter/js/game-management/spawner.js @@ -0,0 +1,79 @@ +function Random_Spawner() { + + this.gameState = game.manager.gameState; + this.id = 10; + + this.nbrSpawns = 0; + this.lastSpawn = 0; + this.nextSpawn = -1; + + this.heat = 0; + this.ratemin = 1400; + this.ratemax = 4000; + + this.spawnon = true; + + this.platforms = me.game.world.getChildByType(game.Platform); + + var r = me.game.viewport.width - 2; + var m = Math.floor(r / 2); + this.robotspawns = [ + [2, 0], [2, 80], [2, 120], + [m, 0], [m + 17, 75], + [r, 5], [r, 60], [r, 175]] + + this.update = function (dt) { + + if (!this.spawnon) return; + + this.lastSpawn += dt; + if (this.nbrSpawns == 0 && this.lastSpawn < 4000) return; + if (this.lastSpawn < this.nextSpawn) return; + + this.lastSpawn = 0; + this.nextSpawn = commons.calc.randomInt(this.ratemin, this.ratemax); + this.spawnOne(); + }; + this.heatup = function () { + + this.nextSpawn -= 1000; + this.ratemin--; + this.ratemax--; + + if (this.heat % 5 == 1) { + this.spawnOne() + this.ratemax -= 2; + } + }; + this.spawnOne = function () { + this.nbrSpawns % 5 == 1 ? this.addBadGuyRed() : this.addBadGuyRunbot(); + this.nbrSpawns++; + }; + + this.addPowerUp = function(){ + + var powerUp = me.pool.pull("powerup", 0, 334); + me.game.world.addChild(powerUp, 18); + }; + this.addBadGuyRed = function(){ + + var spawnnbr = commons.calc.randomInt(0, this.platforms.length-1); + var spawnOn = this.platforms[spawnnbr]; + var posX = commons.calc.randomInt(spawnOn.pos.x , spawnOn.pos.x + spawnOn.width); + var badGuy = me.pool.pull("badguy-red", posX, spawnOn.pos.y, {mpid: this.id++}); + me.game.world.addChild(badGuy, 12); + this.gameState.entities[badGuy.mpid] = badGuy; + }; + this.addBadGuyRunbot = function(){ + + var spawnnbr = commons.calc.randomInt(0, this.robotspawns.length-1); + var spawnpos = this.robotspawns[spawnnbr]; + + var badGuy = me.pool.pull("badguy-runbot", spawnpos[0], spawnpos[1], {mpid: this.id++}); + me.game.world.addChild(badGuy, 12); + this.gameState.entities[badGuy.mpid] = badGuy; + }; + this.stop = function(){ + + } +}; diff --git a/Games/Contra-Shooter/js/game.js b/Games/Contra-Shooter/js/game.js new file mode 100644 index 0000000000..c5b6639642 --- /dev/null +++ b/Games/Contra-Shooter/js/game.js @@ -0,0 +1,82 @@ + +/* Game namespace */ +var game = { + + onload : function () { + if (!me.video.init(1280, 960, {wrapper : "screen", scale : "auto"})) { + alert("Your browser does not support HTML5 canvas."); + return; + } + me.audio.init("mp3,ogg"); + me.sys.pauseOnBlur = false; + me.loader.preload(game.resources, this.loaded.bind(this)); + }, + + loaded : function () { + + this.texture = {}; + this.texture.badguys = new me.video.renderer.Texture( + me.loader.getJSON("bad-guys"), + me.loader.getImage("bad-guys") + ); + this.texture.goodguys = new me.video.renderer.Texture( + me.loader.getJSON("bill-lance"), + me.loader.getImage("bill-lance") + ); + + + game.constants.hydrate(); + + me.pool.register("goodguy-bill", game.GoodGuyBill); + me.pool.register("goodguy-server-npc", game.GoodGuyServerNpc); + me.pool.register("badguy-red", game.BadguyRed); + me.pool.register("badguy-runbot", game.BadguyRunbot); + me.pool.register("bullet", game.Bullet); + me.pool.register("platform", game.Platform); + me.pool.register("powerup", game.Powerup); + me.pool.register("explosion-1", game.Explosion1); + + me.state.set("state-splash", new game.ScreenSplash()); + me.state.set("state-multiplayer-play", new game.MultiplayerPlayScreen()); + me.state.set("state-multiplayer-join", new game.MultiplayerJoinScreen()); + me.state.set("state-test-play", new game.TestPlayScreen()); + + me.state.transition("fade", "#CCCCCC", 250); + me.state.change("state-splash"); + } +}; + +game.constants = { + + Server_url : "localhost", + //Server_url : "50.112.50.11", + + Player_width : 40, + Player_height : 40, + Player_veloXmax : 2, + Player_veloYmax : 13, + Player_reloadtime : 400, + + Bullet_radius : 4, + Bullet_diameter : 8, + Bullet_velo : 8, + + Player_spawnDuration : 75, + Player_dyingDuration : 3500, + Player_deadDuration : 100, + Player_startlives : 8, + + Character_Bill : {name : "Bill", sprite_prefix : "Bill"}, + Character_Lance : {name : "Lance", sprite_prefix : "Lance"}, + + Game_modes : [ + {title: "Survive", options : {}, toState : "state-test-play"}, + {title: "Multiplayer", options : {}, toState : "state-multiplayer-join"} + ], + + hydrate : function(){ + + this.font1 = new me.Font("Arial", 42, '#FFFFFF'); + this.font1.textAlign = "center"; + } +}; diff --git a/Games/Contra-Shooter/js/players/ai-basics.js b/Games/Contra-Shooter/js/players/ai-basics.js new file mode 100644 index 0000000000..045e1136ac --- /dev/null +++ b/Games/Contra-Shooter/js/players/ai-basics.js @@ -0,0 +1,70 @@ + +function AIRunner(npc, options){ + + /*-------------------------------- + * Vars + *--------------------------------*/ + options = options || {}, + this.npc = npc; + this.input = new PlayerInput(); + + this.direction = this.npc.pos.x < (me.game.viewport.width / 2) ? 1 : -1; + this.input.left = this.direction < 0 ? 1 : 0; + this.input.right = this.direction > 0 ? 1 : 0; + + /*-------------------------------- + * Get Input from AI + *--------------------------------*/ + this.getInput = function(dt){ + this.npc.flags.setFlag('direction', this.direction); + return this.input; + } +} + +function AISniper(npc, options){ + + /*-------------------------------- + * Vars + *--------------------------------*/ + options = options || {}, + this.npc = npc; + this.target = options.target || game.manager.gameState.fpu; + this.input = new PlayerInput(); + + this.waitToFire = 1000; + this.waitDt = 0; + this.fireSeq = 0; + + /*-------------------------------- + * Get Input from AI + *--------------------------------*/ + this.getInput = function(dt){ + if(!this.target) return; + + this.input.clear(); + this.getInput_directions(dt); + return this.input.clone(); + }; + + this.getInput_directions = function(dt){ + + const dx = commons.calc.dxToThing(this.npc, this.target); + const dy = commons.calc.upOrDownToThing(this.npc, this.target); + + if(dx > 0){ + this.input.right = 1; + this.npc.flags.setFlag('direction', 1); + } + else if(dx < 0){ + this.input.left = 1; + this.npc.flags.setFlag('direction', -1); + } + + if(dy < this.npc.height){ + //this.input.up = 1; + } + else if(dy > this.npc.height){ + //this.input.down = 1; + } + }; +} diff --git a/Games/Contra-Shooter/js/players/ai-input-servercommands.js b/Games/Contra-Shooter/js/players/ai-input-servercommands.js new file mode 100644 index 0000000000..131e68b347 --- /dev/null +++ b/Games/Contra-Shooter/js/players/ai-input-servercommands.js @@ -0,0 +1,43 @@ +function AI_ServerCommands(player){ + + /*-------------------------------- + * Input to Track + *--------------------------------*/ + this.player = player; + + /*-------------------------------- + * Construct + *--------------------------------*/ + this.getInput = function(dt, serverCommands){ + + //set up... + //parse server commands + const serverX = parseInt(serverCommands[3]); + const serverY = parseInt(serverCommands[4]); + //const serverAction = parseInt(serverCommands[8]); + const serverAiming = parseInt(serverCommands[9]); + + //step... + //build the input + const input = new PlayerInput(); + + const dx = serverX - this.player.pos.x; //if > 0 ... need to go right + const dy = this.player.pos.y - serverY; //if > 0 ... need to jump + + input.jump = dy > 0 ? 1 : 0; + input.left = dx < 0 ? 1 : 0; + input.right = dx > 0 ? 1 : 0; + input.up = 0; + input.down = 0; + input.shooting = 0; + + //set direction flag + if(input.left) this.player.flags.setFlag('direction', -1); + if(input.right) this.player.flags.setFlag('direction', 1); + + //finally... + //return the input + console.log('server commands : ' + serverX + ' : ' + this.player.mpid + ' : ' + input.left + ' : ' + dx); + return input; + }; +} diff --git a/Games/Contra-Shooter/js/players/ai-input-user.js b/Games/Contra-Shooter/js/players/ai-input-user.js new file mode 100644 index 0000000000..2f985bf142 --- /dev/null +++ b/Games/Contra-Shooter/js/players/ai-input-user.js @@ -0,0 +1,29 @@ +function AI_UserInput(player){ + + /*-------------------------------- + * Input to Track + *--------------------------------*/ + this.player = player; + + /*-------------------------------- + * Get Input + *--------------------------------*/ + this.getInput = function(){ + + //read input from user controls + const input = new PlayerInput(); + input.jump = me.input.isKeyPressed('jump') ? 1 : 0; + input.left = me.input.isKeyPressed('left') ? 1 : 0; + input.right = me.input.isKeyPressed('right') ? 1 : 0; + input.up = me.input.isKeyPressed('up') ? 1 : 0; + input.down = me.input.isKeyPressed('down') ? 1 : 0; + input.shooting = me.input.isKeyPressed('shoot') ? 1 : 0; + + //set direction flag + if(input.left) this.player.flags.setFlag('direction', -1); + if(input.right) this.player.flags.setFlag('direction', 1); + + //return ai results as input + return input; + }; +} diff --git a/Games/Contra-Shooter/js/players/badguy-runbot.js b/Games/Contra-Shooter/js/players/badguy-runbot.js new file mode 100644 index 0000000000..c632c659ee --- /dev/null +++ b/Games/Contra-Shooter/js/players/badguy-runbot.js @@ -0,0 +1,80 @@ +game.BadguyRunbot = me.Entity.extend({ + + init : function (x, y, config) { + + //initialization + //variables + this.mpid = config.mpid; + this.name = "Run Robot"; + this.chartype = "BadGuy"; + + var settings = { + width: game.constants.Player_width, + height: game.constants.Player_height + }; + + this._super(me.Entity, 'init', [x, y, settings]); + this.alwaysUpdate = true; + this.body.setVelocity(game.constants.Player_veloXmax * 1.3, game.constants.Player_veloYmax); + + this.type = "runbot"; + + //composition + //delegates for stuff + this.flags = new Flags(this); + this.lifecyle = new PlayerLifeState(this); + this.input = new PlayerInput(); + this.movement = new MovementInput(this); + this.ai = new AIRunner(this); + this.displayHandler = new Display_BadGuyRunbot(this); + }, + + update : function (dt) { + + if (this.pos.y > me.game.viewport.height) { + return this.removeIt(); + } + else if(this.pos.x > me.game.viewport.width || this.pos.x < 0){ + return this.removeIt(); + } + + this.input = this.ai.getInput(dt); + this.movement.update(dt); + this.displayHandler.update(dt); + + this.body.update(dt); + me.collision.check(this); + + return this._super(me.Entity, 'update', [dt]) || true; + }, + + onCollision : function (response, other) { + + switch (other.type){ + + case 'platform' : + return commons.collisions.withPlatformDemo(this, response, other); + + case 'bullet' : + if(other.pid != game.manager.gameState.fpu.mpid){ + return false; + } + if(commons.collisions.withBullet(this, response, other)) { + game.manager.postBulletDamage(this, other); + this.processDeath(); + } + return false; + + + default : + return false; + } + }, + removeIt(){ + me.game.world.removeChild(this); + }, + processDeath : function() { + me.game.world.removeChild(this); + return false; + }, +}); diff --git a/Games/Contra-Shooter/js/players/badguy-shooter.js b/Games/Contra-Shooter/js/players/badguy-shooter.js new file mode 100644 index 0000000000..876a0e0bf4 --- /dev/null +++ b/Games/Contra-Shooter/js/players/badguy-shooter.js @@ -0,0 +1,75 @@ +game.BadguyRed = me.Entity.extend({ + + init : function (x, y, config) { + + //initialization + //variables + this.mpid = config.mpid; + this.name = "BadGuy Red"; + this.chartype = "BadGuy"; + + var settings = { + width: game.constants.Player_width, + height: game.constants.Player_height + }; + + this._super(me.Entity, 'init', [x, y, settings]); + this.alwaysUpdate = true; + this.body.setVelocity(0, game.constants.Player_veloYmax); + + + //composition + //delegates for stuff + this.flags = new Flags(this); + this.lifecyle = new PlayerLifeState(this); + this.input = new PlayerInput(); + this.movement = new MovementInput(this); + this.ai = new AISniper(this); + this.gun = new Gun(this, {reloadTime : 1220}); + this.displayHandler = new Display_BadGuyRed(this); + }, + + update : function (dt) { + + this.lifecyle.update(dt); + this.input = this.ai.getInput(dt); + this.gun.fire(dt); + + //step... + //clear dx/dy then move + //this.input.clear(); + this.movement.update(dt); + this.displayHandler.update(dt); + + this.body.update(dt); + me.collision.check(this); + + return this._super(me.Entity, 'update', [dt]) || true; + }, + + onCollision : function (response, other) { + + switch (other.type){ + + case 'platform' : + return commons.collisions.withPlatformDemo(this, response, other); + + case 'bullet' : + if(commons.collisions.withBullet(this, response, other)) { + game.manager.postBulletDamage(this, other); + this.processDeath(); + } + return false; + + default : + return false; + } + }, + removeIt(){ + me.game.world.removeChild(this); + }, + processDeath : function() { + me.game.world.removeChild(this); + return false; + }, +}); diff --git a/Games/Contra-Shooter/js/players/delegate-collision-logics.js b/Games/Contra-Shooter/js/players/delegate-collision-logics.js new file mode 100644 index 0000000000..3c41aa1a58 --- /dev/null +++ b/Games/Contra-Shooter/js/players/delegate-collision-logics.js @@ -0,0 +1,44 @@ + +var commons = commons || {}; + +commons.collisions = { + + withPlatformDemo : function(that, response, other){ + + if (!that.body.falling){ + return false; + } + + // Shortest overlap would move the player upward + // The velocity is reasonably fast enough to have penetrated to the overlap depth + // Disable collision on the x axis + if(response.overlapV.y > 0 && (~~that.body.vel.y >= ~~response.overlapV.y)){ + response.overlapV.x = 0; + return true; + } + return false; + }, + + processedColliders : {}, + + withBullet : function(player, response, other){ + + if(other.pid === player.mpid) { + return false; + } + if(this.processedColliders[other.bid]){ + return false; + } + this.processedColliders[other.bid] = 1; + return true; + }, + + withBadguy : function(player, response, other){ + + if(this.processedColliders[other.mpid]){ + return false; + } + this.processedColliders[other.mpid] = 1; + return true; + } +} diff --git a/Games/Contra-Shooter/js/players/delegate-display-badguys.js b/Games/Contra-Shooter/js/players/delegate-display-badguys.js new file mode 100644 index 0000000000..fd187c7a67 --- /dev/null +++ b/Games/Contra-Shooter/js/players/delegate-display-badguys.js @@ -0,0 +1,78 @@ + +function Display_BadGuyRed(player, spriteName){ + + /*-------------------------------- + * Vars + *--------------------------------*/ + this.player = player; + var sn = spriteName || "BadGuys"; + + var animationmap = [ + {name : "horiz", frames : [2, 3]}, + {name : "up", frames : [4, 5]}, + {name : "down", frames : [1]}, + ]; + + this.player.renderable = commons.me.textureSpriteHelper(game.texture.badguys, sn, animationmap); + this.player.renderable.setCurrentAnimation('horiz'); + + /*-------------------------------- + * Update + *--------------------------------*/ + this.update = function(dt) { + + //step 1.. + //update face direction + const direction = player.flags.getFlag('direction') || 1; + (direction == -1) ? this.player.renderable.flipX(true) : this.player.renderable.flipX(false); + + //step 2... + //get spriteName ... update sprite + const spriteName = this.update_getSpriteName(); + this.update_Sprite(spriteName); + this.player.renderable.update(dt); + }; + this.update_getSpriteName = function(){ + + const aiming = commons.playerUtils.getAimDirection(this.player); + + //aim up + if(aiming == 2 || aiming == 3){ + return 'up'; + } + //aim down + if(aiming == 4 || aiming == 5){ + return 'down' + } + //else horiz + return 'horiz'; + }; + this.update_Sprite = function(toState) { + if (!this.player.renderable.isCurrentAnimation(toState)) { + this.player.renderable.setCurrentAnimation(toState); + } + }; +} + + +function Display_BadGuyRunbot(player, spriteName) { + + /*-------------------------------- + * Vars + *--------------------------------*/ + this.player = player; + var sn = spriteName || "BadGuys"; + + var animationmap = [ + {name: "run", frames: [17, 18, 19, 20, 21, 22]}, + ]; + + this.player.renderable = commons.me.textureSpriteHelper(game.texture.badguys, sn, animationmap); + this.player.renderable.setCurrentAnimation('run'); + + this.update = function (dt) { + //update face direction + const direction = player.flags.getFlag('direction') || 1; + (direction == -1) ? this.player.renderable.flipX(true) : this.player.renderable.flipX(false); + } +} diff --git a/Games/Contra-Shooter/js/players/delegate-display-goodguys.js b/Games/Contra-Shooter/js/players/delegate-display-goodguys.js new file mode 100644 index 0000000000..e5c64f87aa --- /dev/null +++ b/Games/Contra-Shooter/js/players/delegate-display-goodguys.js @@ -0,0 +1,140 @@ + + +function Display_BillLance(player, spriteName){ + + /*-------------------------------- + * Variables + *--------------------------------*/ + this.player = player; + + const sn = spriteName || game.constants.Character_Bill.sprite_prefix; + + const animationmap = [ + {name : "shooting", frames : [5, 6]}, + {name : "idle", frames : [5]}, + {name : "walk", frames : [26, 27, 28, 29, 30]}, + {name : "jump", frames : [37, 38, 39, 40]}, + {name : "duck", frames : [36]}, + {name : "aimup", frames : [8]}, + {name : "aimdownwalk", frames : [18, 19, 20]}, + {name : "aimupwalk", frames : [12, 13, 14]}, + {name : "dying", frames : [46, 47, 48, 49, 50], delay : 500}, + {name : "dead", frames : [50]} + ]; + + /*-------------------------------- + * Init / Clean UP + *--------------------------------*/ + this.player.renderable = commons.me.textureSpriteHelper(game.texture.goodguys, sn, animationmap); + + this.highlight = new HL_PlayerWithReload(this.player); + this.highlight.show(); + + this.cleanUp = function(){ + this.highlight && this.highlight.hide(); + }; + + + /*-------------------------------- + * Update + *--------------------------------*/ + this.update = function(dt) { + + //step 1.. + //update face direction + const direction = player.flags.getFlag('direction') || 1; + (direction == -1) ? this.player.renderable.flipX(true) : this.player.renderable.flipX(false); + + //step 2... + //get spriteName ... update sprite + const spriteName = this.update_getSpriteName(); + this.update_Sprite(spriteName); + this.player.renderable.update(dt); + }; + this.update_getSpriteName = function(){ + + const lifecycle = commons.playerUtils.getLifecycleState(this.player); + const input = commons.playerUtils.getCurrentInput(this.player); + const aiming = commons.playerUtils.getAimDirection(this.player); + + const isMoving = commons.playerUtils.isMoving(this.player); + const isAirborne = commons.playerUtils.isAirborne(this.player); + const isShooting = commons.playerUtils.isGunShooting(this.player); + const isInputLeftRight = (input.left || input.right) ? true : false; + + //type 1... + //states that are forced by lifecycle + if(lifecycle == 0){ + return "idle"; + } + if(lifecycle == 2){ + return "dying"; + } + if(lifecycle == 3){ + return "dead"; + } + + + //case... + //any airborne is just jump + if(isAirborne){ + return "jump"; + } + + //case... + //moving l/r .. aimup + if(isInputLeftRight && aiming==2){ + return "aimupwalk"; + } + + //case... + //moving l/r .. aimdown + if(isInputLeftRight && aiming==4){ + return "aimdownwalk"; + } + + //case.. + //moving l/r ... shooting + if(isInputLeftRight && isShooting){ + return "shooting"; + } + + //case.. + //moving l/r ... running + if(isInputLeftRight){ + return "walk"; + } + + //case... + //aiming up ... up + if(aiming == 3){ + return "aimup"; + } + + //case... + //input down ... duck + if(input.down){ + return "duck"; + } + + //case... + //stand still...isShooting + if(isShooting){ + return "shooting"; + } + + //default... + //return idle + return "idle"; + }; + + this.update_Sprite = function(toState, f) { + + if (this.player.renderable.isCurrentAnimation(toState)) { + return false; + } + this.player.renderable.setCurrentAnimation(toState, f); + return true; + }; + +} diff --git a/Games/Contra-Shooter/js/players/delegate-gun.js b/Games/Contra-Shooter/js/players/delegate-gun.js new file mode 100644 index 0000000000..3505aca89a --- /dev/null +++ b/Games/Contra-Shooter/js/players/delegate-gun.js @@ -0,0 +1,77 @@ + +function Gun(player, options){ + + /*-------------------------------- + * Input to Track + *--------------------------------*/ + options = options || {}; + this.player = player; + this.isShooting = false; + this.dtLastFire = 0; + this.reloadTime = options.reloadTime || game.constants.Player_reloadtime; + this.reloadPerc = 1; + + /*-------------------------------- + * Construct / Reset + *--------------------------------*/ + this.resetGunValues = function() { + this.isShooting = false; + this.dtLastFire = 0; + this.reloadPerc = 1; + }; + + + /*-------------------------------- + * Idle ... not shooting + *--------------------------------*/ + this.idle = function (dt) { + //iterate + this.dtLastFire += dt; + this.reloadPerc = Math.min((this.dtLastFire / this.reloadTime), 1); + + //is still shooting/reloading? + //if so ... just return + if(this.isShooting && (this.dtLastFire > this.reloadTime)){ + this.isShooting = false; + } + }; + + + /*-------------------------------- + * Fire ... shoot bullet if can + *--------------------------------*/ + this.fire = function (dt) { + + //escape if...not active player + if(!commons.playerUtils.isLifecycleAlive(this.player)){ + this.resetGunValues(); + return; + } + + //iterate + this.dtLastFire += dt; + this.reloadPerc = Math.min((this.dtLastFire / this.reloadTime), 1); + + //is still shooting/reloading? + //if so ... just return + if(this.isShooting && (this.dtLastFire > this.reloadTime)){ + this.isShooting = false; + } + else if(this.isShooting){ + return; + } + + //step 2.. + //fire the gun + const fireValues = commons.playerUtils.getBarrelPosition(this.player); + const fireX = fireValues[0] - game.constants.Bullet_radius; + const fireY = fireValues[1] - 2 * game.constants.Bullet_radius; + const dirX = fireValues[2]; + const dirY = fireValues[3]; + + game.manager.postBullet(this.player, fireX, fireY, dirX, dirY); + this.dtLastFire = 0; + this.reloadPerc = 0; + this.isShooting = true; + } +} diff --git a/Games/Contra-Shooter/js/players/delegate-movement-input.js b/Games/Contra-Shooter/js/players/delegate-movement-input.js new file mode 100644 index 0000000000..708ccbd2b9 --- /dev/null +++ b/Games/Contra-Shooter/js/players/delegate-movement-input.js @@ -0,0 +1,23 @@ + +function MovementInput(player){ + + this.player = player; + + this.update = function (dt) { + + const input = this.player.input; + + //get acceleration changes of player for x-axis + //apply change in X velocity + const uX = input.left == 1 ? -1 : (input.right == 1 ? 1 : 0); + this.player.body.vel.x = uX * this.player.body.accel.x * me.timer.tick; + + //if jump is pressed + //and player is not currently in air + //jump player up + if(input.jump){ //&& !commons.playerUtils.isAirborne(this.player)){ + this.player.body.vel.y = -1 * 14 * this.player.body.maxVel.y * me.timer.tick; + this.player.body.jumping = true; + } + } +} diff --git a/Games/Contra-Shooter/js/players/goodguy-bill.js b/Games/Contra-Shooter/js/players/goodguy-bill.js new file mode 100644 index 0000000000..157842e366 --- /dev/null +++ b/Games/Contra-Shooter/js/players/goodguy-bill.js @@ -0,0 +1,94 @@ +game.GoodGuyBill = me.Entity.extend({ + + init : function (x, y, config) { + + //initialization + //variables + this.mpid = config.mpid; + this.name = this.mpid; + this.team = config.team; + this.character = config.character || game.constants.Character_Bill; + this.chartype = "GoodGuy"; + this.lives = config.startlives || game.constants.Player_startlives; + this.score = 0; + + // melonjs + // constructor and setup + this.meSettings = { + width: game.constants.Player_width, + height: game.constants.Player_height + }; + this._super(me.Entity, 'init', [x, y, this.meSettings]); + this.alwaysUpdate = true; + this.body.setVelocity(game.constants.Player_veloXmax, game.constants.Player_veloYmax); + + + //composition + //delegates for stuff + this.flags = new Flags(this); + this.lifecyle = new PlayerLifeState(this); + this.input = new PlayerInput(); + this.ai = new AI_UserInput(this); + this.movement = new MovementInput(this); + this.gun = new Gun(this); + this.displayHandler = new Display_BillLance(this, this.character.sprite_prefix); + }, + + update : function (dt) { + + //step 1... + //delegate updates + this.input = this.ai.getInput(dt); + this.lifecyle.update(dt); + this.movement.update(dt); + this.displayHandler.update(dt); + + //step 2... + //special commands + if(this.input.shooting){ + this.gun.fire(dt); + } + else{ + this.gun.idle(dt); + } + + //step 2... + //melon updates / checks + this.body.update(dt); + me.collision.check(this); + + //final... + game.manager.postPlayerUpdate(this); + return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0); + }, + + + onCollision : function (response, other) { + + if (other.type == 'platform'){ + return commons.collisions.withPlatformDemo(this, response, other); + } + + if(!commons.playerUtils.isLifecycleAlive(this)){ + return false; + } + else if (other.type == 'runbot' && commons.collisions.withBadguy(this, response, other)){ + this.onCollision_takeDamage(other); + commons.game.effect_ExplodeOnTarget(this); + } + else if(other.type == 'bullet' && commons.collisions.withBullet(this, response, other)){ + this.onCollision_takeDamage(other); + game.manager.postBulletDamage(this, other); + } + + return false; + }, + + onCollision_takeDamage(other){ + this.lifecyle.setState(2); + this.lives--; + if(this.lives == 0){ + game.manager.onGameOver(); + } + } +}); diff --git a/Games/Contra-Shooter/js/players/goodguy-servernpc.js b/Games/Contra-Shooter/js/players/goodguy-servernpc.js new file mode 100644 index 0000000000..a3eace8174 --- /dev/null +++ b/Games/Contra-Shooter/js/players/goodguy-servernpc.js @@ -0,0 +1,99 @@ +game.GoodGuyServerNpc = me.Entity.extend({ + + /*-------------------------------- + * Init + *--------------------------------*/ + init : function (x, y, config) { + + //initialization + //variables + this.mpid = config.mpid; + this.name = this.mpid; + this.team = config.team; + this.character = config.character || game.constants.Character_Lance; + this.lives = config.startlives || game.constants.Player_startlives; + this.score = 0; + + //from server + //variables + this.serverCommands = []; + + // melonjs + // constructor and setup + this.meSettings = { + width: game.constants.Player_width, + height: game.constants.Player_height + }; + this._super(me.Entity, 'init', [x, y, this.meSettings]); + this.alwaysUpdate = true; + this.body.setVelocity(game.constants.Player_veloXmax, game.constants.Player_veloYmax); + + + //composition + //delegates for stuff + this.flags = new Flags(this); + this.lifecyle = new PlayerLifeState(this); + this.input = new PlayerInput(); + this.ai = new AI_ServerCommands(this); + this.movement = new MovementInput(this); + this.gun = new Gun(this); + this.displayHandler = new Display_BillLance(this, this.character.sprite_prefix); + }, + + + + /*-------------------------------- + * Game Loop / Manage + *--------------------------------*/ + update : function (dt) { + + //step 1... + //delegate updates + this.input = this.ai.getInput(dt, this.serverCommands); + this.lifecyle.update(dt); + this.movement.update(dt); + this.displayHandler.update(dt); + + //step 2... + //special commands + if(this.input.shooting){ + this.gun.fire(dt); + } + else{ + this.gun.idle(dt); + } + + //step 2... + //melon updates / checks + this.body.update(dt); + me.collision.check(this); + + //final... + game.manager.postPlayerUpdate(this); + return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0); + }, + + onCollision : function (response, other) { + + if (other.type == 'platform'){ + return commons.collisions.withPlatformDemo(this, response, other); + } + return false; + }, + + removeIt : function() { + this.displayHandler.cleanUp(); + me.game.world.removeChild(this); + }, + + /*-------------------------------- + * Server Commands + *--------------------------------*/ + applyServerRemoveCommand : function(command){ + this.removeIt(); + }, + applyServerCommands : function (serverCommands){ + this.serverCommands = serverCommands; + }, + +}); diff --git a/Games/Contra-Shooter/js/players/player-input.js b/Games/Contra-Shooter/js/players/player-input.js new file mode 100644 index 0000000000..12dc3f410b --- /dev/null +++ b/Games/Contra-Shooter/js/players/player-input.js @@ -0,0 +1,55 @@ +function PlayerInput(){ + + /*-------------------------------- + * Input to Track + *--------------------------------*/ + this.jump = 0; + this.up = 0; + this.down = 0; + this.left = 0; + this.right = 0; + this.shooting = 0; + + /*-------------------------------- + * Assemble / Clear + *--------------------------------*/ + this.clone = function(){ + const i = new PlayerInput(); + i.jump = this.jump; + i.up = this.up; + i.down = this.down; + i.left = this.left; + i.right = this.right; + i.shooting = this.shooting; + return i; + }; + this.clear = function(){ + this.copyFrom({}); + }; + this.copyFrom = function(other){ + + this.jump = other.jump || 0; + this.up = other.up || 0; + this.down = other.down || 0; + this.left = other.left || 0; + this.right = other.right || 0; + this.shooting = other.shooting || 0; + }; + + /*-------------------------------- + * Inspect + *--------------------------------*/ + this.isAnyDirectionPressed = function(){ + if(this.jump > 0) return true; + if(this.up > 0) return true; + if(this.down > 0) return true; + if(this.left > 0) return true; + if(this.right > 0) return true; + return false; + }; + this.isAnyPressed = function(){ + if(this.isAnyDirectionPressed) return true; + if(this.shooting > 0) return true; + return false; + } +} diff --git a/Games/Contra-Shooter/js/players/player-state.js b/Games/Contra-Shooter/js/players/player-state.js new file mode 100644 index 0000000000..a4439dd551 --- /dev/null +++ b/Games/Contra-Shooter/js/players/player-state.js @@ -0,0 +1,46 @@ + +/* ---------------------------------------- + * Players have a pretty finite set of states + * and can be described across a couple parameters + * + * state -> 0 = spawning + * 1 = alive + * 2 = dying + * 3 = dead + * -------------------------------------------------*/ + +function PlayerLifeState(player){ + + this.player = player; + this.state = 0; + this.stateTimer = 0; + + /*-------------------------------- + * Send Change Requests + *--------------------------------*/ + this.setState = function(state, forceTime){ + this.state = state; + this.stateTimer = 0; + }; + + /*-------------------------------- + * Update state per timer as appropriate + *--------------------------------*/ + this.update = function(dt){ + + this.stateTimer += dt; + + if(this.state == 0 && (this.stateTimer > game.constants.Player_spawnDuration)){ + this.state = 1; + this.stateTimer = 0; + } + else if(this.state == 2 && (this.stateTimer > game.constants.Player_dyingDuration)){ + this.state = 3; + this.stateTimer = 0; + } + else if(this.state == 3 && (this.stateTimer > game.constants.Player_deadDuration)){ + this.state = 0; + this.stateTimer = 0; + } + } +} diff --git a/Games/Contra-Shooter/js/players/player-utils.js b/Games/Contra-Shooter/js/players/player-utils.js new file mode 100644 index 0000000000..fa07af3c64 --- /dev/null +++ b/Games/Contra-Shooter/js/players/player-utils.js @@ -0,0 +1,148 @@ +var commons = commons || {}; + +commons.playerUtils = { + + /*-------------------------------- + * Get lifecyle Info + *--------------------------------*/ + isLifecycleAlive : function(player){ + return ( (player.lifecyle.state == 1) ? true : false); + }, + getLifecycleState: function(player){ + return player.lifecyle.state; + }, + + /*-------------------------------- + * Input / Movement Info + *--------------------------------*/ + isMoving: function(player){ + if(this.isAirborne(player)) return true; + if(player.body.vel.x > 0.5) return true; + if(player.body.vel.x < 0.5) return true; + return false; + }, + isAnyInput: function(player){ + const input = player.input; + return input.isAnyPressed(); + }, + isAirborne: function(player){ + return ( (player.body.jumping || player.body.falling) ? true : false); + }, + getCurrentInput: function(player){ + return player.input; + }, + + /*-------------------------------- + * Get Gun Info + *--------------------------------*/ + isGunShooting: function(player){ + const gun = player.gun; + if(!gun) return false; + return gun.isShooting; + }, + getGunReloadPercent: function(player){ + const gun = player.gun; + if(!gun) return false; + return gun.reloadPerc; + }, + + /*-------------------------------- + * Player Center + *--------------------------------*/ + getCenterPosition: function(player){ + const centerX = player.pos.x + (.5 * player.width); + const centerY = player.pos.y + (.5 * player.height); + return [centerX, centerY]; + }, + + /*-------------------------------- + * Player Barrel Position + *--------------------------------*/ + getBarrelPosition: function(player){ + + const aiming = this.getAimDirection(player); + const direction = player.flags.getFlag('direction') || 1; + const center = this.getCenterPosition(player); + + switch (aiming) { + + //horizontal + case 1 : + var x = center[0] + (direction * .5 * player.width); + var y = center[1]; + var uX = direction; + var uY = 0; + return [x, y, uX, uY]; + break; + + //up and angle + case 2 : + var x = center[0] + (direction * .5 * player.width); + var y = player.pos.y; + var uX = direction; + var uY = -1; + return [x, y, uX, uY]; + break; + + //up + case 3 : + var x = center[0] - (.5 * game.constants.Bullet_diameter); + var y = player.pos.y - game.constants.Bullet_diameter; + var uX = 0; + var uY = -1; + return [x, y, uX, uY]; + break; + + //down and angle + case 4 : + var x = center[0] + (direction * (player.width + game.constants.Bullet_diameter)); + var y = player.pos.y + player.height; + var uX = direction; + var uY = 1; + return [x, y, uX, uY]; + break; + + //down + case 5 : + var x = center[0] - (.5 * game.constants.Bullet_diameter); + var y = player.pos.y + player.height + game.constants.Bullet_diameter; + var uX = 0; + var uY = 1; + return [x, y, uX, uY]; + break; + + default : + return [0, 0, 0, 0]; + } + }, + + + /*-------------------------------- + * Player Look Direction + * aiming -> 1 = Horizontal + * 2 = Up and Angle + * 3 = Up + * 4 = Down and Angle + * 5 = Down + *--------------------------------*/ + getAimDirection: function(player){ + + const input = player.input; + + if((input.left || input.right) && input.up){ + return 2; + } + else if(input.up){ + return 3; + } + else if((input.left || input.right) && input.down){ + return 4; + } + else if(input.down){ + return 5; + } + else{ + return 1; + } + } +} diff --git a/Games/Contra-Shooter/js/robot-brains/rb-calc.js b/Games/Contra-Shooter/js/robot-brains/rb-calc.js new file mode 100644 index 0000000000..12130c8921 --- /dev/null +++ b/Games/Contra-Shooter/js/robot-brains/rb-calc.js @@ -0,0 +1,63 @@ +var rb = rb || {}; +rb.calc = {}; + +rb.calc.distance = { + + d: function (from, target) { + var dx = from.pos.x - target.pos.x; + var dy = from.pos.y - target.pos.y; + return Math.sqrt(Math.pow(dx, 2), Math.pow(dy, 2)); + }, + x_point: function (from, target) { + return from.pos.x - target.pos.x; + }, + y_point: function (from, target) { + return from.pos.y - target.pos.y; + }, + + angleBetween : function(from, target) { + return Math.atan2( (from.pos.y - target.pos.y), (from.pos.x - from.pos.y)); + }, +}; + +rb.calc.motion = { + + apply_vel : function(dt, vel, from){ + from = from || 0; + + return ( vel * dt ) + from; + }, + + apply_acc : function(dt, acc, velprime, from){ + from = from || 0; + velprime = velprime || 0; + + return ( (.5 * acc * dt * dt) + (dt * velprime) + (from) ); + }, + + calc_jumpTime : function(g, jh) { + + return Math.sqrt( (2 * jh) / g ); + }, + + calc_jumpHeight : function(g, velJump) { + + return (velJump * velJump) / (2 * g); + }, +}; + +rb.calc.nbr = { + + isBetween : function(val, min, max){ + return (val < min) ? (false) : (val > max ? false : true); + }, + clamp : function(val, min, max){ + return (val < min) ? (min) : (val > max ? max : val); + }, + + randomInt : function (min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; + } +}; \ No newline at end of file diff --git a/Games/Contra-Shooter/js/robot-brains/rb-interpolate.js b/Games/Contra-Shooter/js/robot-brains/rb-interpolate.js new file mode 100644 index 0000000000..7fb8e31008 --- /dev/null +++ b/Games/Contra-Shooter/js/robot-brains/rb-interpolate.js @@ -0,0 +1,9 @@ +var rb = rb || {}; + +rb.interpolation = { + + simple_vel : function(dt, entity){ + + return {x : (entity.vel.x * dt), y : (entity.vel.y * dt) }; + } +} diff --git a/Games/Contra-Shooter/js/robot-brains/rb-pathfinder.js b/Games/Contra-Shooter/js/robot-brains/rb-pathfinder.js new file mode 100644 index 0000000000..6fbd9ce27f --- /dev/null +++ b/Games/Contra-Shooter/js/robot-brains/rb-pathfinder.js @@ -0,0 +1,121 @@ +var rb = rb || {}; + +rb.paths = {}; + +/*----------------------------------- + * Path Graph + * + * Input Options - + * standables -> list of items we can stand on (platforms) + * g -> gravity applied to users of the path + * jumpVel -> jump velocity available to users of the path + * xVel -> max left-right velocity available to users of the path + * + * Surfaces - nodes in path graph which represent what users of the path can stand on + * id + * left, right, top + * connections -> Connection[] + * + * Connection - represents a possible surface to surface traversal + * toId, + * fromX, fromy, toX, toY, + * command (jump-left | jump-right | jump-up) + * + * ----------------------------------*/ +rb.paths.PathGraph = function(options) { + + this.options = options || {}; + + this.surfaces = {}; + this.jumpHeight = rb.calc.motion.calc_jumpHeight(options.g, options.jumpVel); + this.jumpDt = rb.calc.motion.calc_jumpTime(options.g, this.jumpHeight); + this.jumpW = rb.calc.motion.apply_vel(this.jumpDt * 2, options.xVel); + + this.buildGraph = function (options) { + + this.options = options || this.options; + + this.options.standables.forEach(standable => { + this.pushSurface(standable.GUID, standable.left, standable.right, standable.top); + }); + }; + + this.pushSurface = function (guid, left, right, top) { + + var newSurface = {guid: guid, left: left, right: right, top: top, connections: []}; + + for(var surfaceKey in this.surfaces){ + var s = this.surfaces[surfaceKey]; + this.addConnections(newSurface, s); + } + this.surfaces[guid] = newSurface; + }; + this.addConnections = function (sA, sB) { + + //from sA -> sB + var dY = sA.top - sB.top; + var dX = this.surfaceDistX(sA, sB); + + if (dY > this.jumpHeight) return; + if (dY < 0) return; + + + if(rb.calc.nbr.isBetween(sA.left, sB.left, sB.right)){ + sA.connections.push( + {toSurface : sB.guid, fromX : sA.left, fromY : sA.top, toX : sA.left, toY : sB.top, command : "jump"} + ); + } + else if(rb.calc.nbr.isBetween(sA.right, sB.left, sB.right)){ + sA.connections.push( + {toSurface : sB.guid, fromX : sA.right, fromY : sA.top, toX : sA.right, toY : sB.top, command : "jump"} + ); + } + else if (rb.calc.nbr.isBetween(dX, -this.jumpW, -1)){ + + sA.connections.push( + {toSurface : sB.guid, fromX : sA.left, fromY : sA.top, toX : sB.right, toY : sB.top, command : "jump"} + ); + } + else if(rb.calc.nbr.isBetween(dX, 1, this.jumpW)){ + + sA.connections.push( + {toId : sB.guid, fromX : sA.right, fromY : sA.top, toX : sB.left, toY : sB.top, command : "jump-right"} + ); + } + }; + this.surfaceDistX = function (sFrom, sTo) { + + if (sFrom.left > sTo.right){ + return -1 * (sFrom.left - sTo.right); + } + + if (sFrom.right < sTo.right){ + return sTo.left - sFrom.right; + } + return 0; + }; +} + +rb.Pathfinder = function(entity, graph){ + + + this.entity = entity; + this.graph = graph; + this.target = {}; + + this.dX = 0; + this.dY = 0; + + this.bestPath = []; + + this.setTarget = function(target){ + this.target = target; + } + + this.updateIt = function(){ + + this.dX = rb.calc.distance.x_point(this.entity, this.target); + this.dY = rb.calc.distance.x_point(this.entity, this.target); + + } +}; diff --git a/Games/Contra-Shooter/js/screens/multiplayer-join.js b/Games/Contra-Shooter/js/screens/multiplayer-join.js new file mode 100644 index 0000000000..272f58657a --- /dev/null +++ b/Games/Contra-Shooter/js/screens/multiplayer-join.js @@ -0,0 +1,73 @@ +game.MultiplayerJoinScreen = me.ScreenObject.extend({ + + /* ------------------------------------------- + * Lifecycle + *-------------------------------------------*/ + onResetEvent : function () { + + me.game.world.addChild(new me.ColorLayer("background", "#000000"), 0); + me.input.bindKey(me.input.KEY.ENTER, "enter", true); + + this.startState_1(); + }, + onDestroyEvent : function () { + me.input.unbindKey(me.input.KEY.ENTER); + me.event.unsubscribe(this.handler_state1); + }, + + + /* ------------------------------------------- + * State 1 -> get user name + *-------------------------------------------*/ + startState_1: function() { + + this.htmlspace = new game.HtmlSpace(2); + new game.HtmlHeader3(this.htmlspace.$htmlspace, "Contra Multiplayer"); + this.input = new game.HtmlTextInput(this.htmlspace.$htmlspace, "name", "text", 12); + + var that = this; + this.handler_state1 = me.event.subscribe(me.event.KEYDOWN, + (action, keycode, edge) => action == "enter" && that.doState_1()); + }, + doState_1: function() { + + var nameVal = this.input.getInputValue() || "bob"; + + game.constants.playerName = nameVal; + me.event.unsubscribe(this.handler_state1); + this.input.destroy(); + this.startState_2(); + }, + + /* ------------------------------------------- + * State 2 -> Set up loading, and trial level + *-------------------------------------------*/ + startState_2 : function() { + this.doState_2(); + }, + doState_2 : function(){ + this.startState_3(); + }, + + + /* ------------------------------------------- + * State 3 -> Try to find a game + *-------------------------------------------*/ + startState_3 : function() { + + var options = { + url : game.constants.Server_url, + playerName : game.constants.playerName, + onGameOver : (e) => me.state.change("state-splash"), + }; + + this.mpg = new GameManager_Multiplayer(options); + var that = this; + this.mpg.joinGame(() => that.finishState_3()); + }, + finishState_3 : function(){ + + this.htmlspace.destroy(); + me.state.change("state-multiplayer-play", {mpg : this.mpg}); + } +}); diff --git a/Games/Contra-Shooter/js/screens/multiplayer-play.js b/Games/Contra-Shooter/js/screens/multiplayer-play.js new file mode 100644 index 0000000000..8a1461cd3b --- /dev/null +++ b/Games/Contra-Shooter/js/screens/multiplayer-play.js @@ -0,0 +1,31 @@ +game.MultiplayerPlayScreen = me.ScreenObject.extend({ + + onResetEvent: function(args) { + + me.levelDirector.loadLevel("contra-lvl-1"); + + // enable the keyboard + me.input.bindKey(me.input.KEY.LEFT, "left"); + me.input.bindKey(me.input.KEY.RIGHT, "right"); + me.input.bindKey(me.input.KEY.UP, "up"); + me.input.bindKey(me.input.KEY.DOWN, "down"); + + me.input.bindKey(me.input.KEY.SPACE, "jump", true); + me.input.bindKey(me.input.KEY.D, "shoot"); + me.input.bindKey(me.input.KEY.F, "shoot"); + + game.manager = args.mpg; + game.manager.startGame(); + this.HUD = new game.HUD.Container(); + me.game.world.addChild(this.HUD); + }, + onDestroyEvent: function() { + + me.input.unbindKey(me.input.KEY.LEFT); + me.input.unbindKey(me.input.KEY.RIGHT); + me.input.unbindKey(me.input.KEY.UP); + me.input.unbindKey(me.input.KEY.DOWN); + me.input.unbindKey(me.input.KEY.SPACE); + me.game.world.removeChild(this.HUD); + } +}); diff --git a/Games/Contra-Shooter/js/screens/splash.js b/Games/Contra-Shooter/js/screens/splash.js new file mode 100644 index 0000000000..d6ebfae8f9 --- /dev/null +++ b/Games/Contra-Shooter/js/screens/splash.js @@ -0,0 +1,114 @@ +game.ScreenSplash = me.ScreenObject.extend({ + + onResetEvent : function () { + + me.game.world.addChild(new me.ColorLayer("background", "#000000"), 0); + this.selected = 0; + + var bg = new me.Sprite(0, 0, {image: me.loader.getImage('img_splashpage')}); + bg.anchorPoint.set(0, 0); + bg.scale(me.game.viewport.width / bg.width, me.game.viewport.height / bg.height); + me.game.world.addChild(bg, 1); + + this.optionsMenu = this.newOptionsMenu(); + me.game.world.addChild(this.optionsMenu, 2); + + me.input.bindKey(me.input.KEY.ENTER, "enter", true); + me.input.bindKey(me.input.KEY.DOWN, "down", true); + me.input.bindKey(me.input.KEY.UP, "up", true); + + var that = this; + this.handler = me.event.subscribe(me.event.KEYDOWN, (action, keycode, edge) => that.onInput(action)); + + this.zoomer = new Zoomer(); + this.zoomer.enable(); + }, + + onDestroyEvent : function () { + me.input.unbindKey(me.input.KEY.ENTER); + me.input.unbindPointer(me.input.pointer.LEFT); + me.event.unsubscribe(this.handler); + }, + + onInput : function(action){ + + switch (action) { + + case "enter" : + me.audio.play("cling"); + me.state.change(game.constants.Game_modes[this.selected].toState); + break; + + case "down" : + if (this.selected < (game.constants.Game_modes.length - 1)) { + this.selected++; + this.optionsMenu.updateselected(this.selected); + } + break + + case "up" : + if (this.selected > 0) { + this.selected--; + this.optionsMenu.updateselected(this.selected); + } + break + } + }, + newOptionsMenu : function (){ + + var closure = { + x : me.game.viewport.width * .2, + y : (me.game.viewport.height / 2 ) * 1.1, + w : me.game.viewport.width * .3, + h : me.game.viewport.height / 2, + selected : this.selected, + }; + + return new (me.Renderable.extend ({ + + init : function () { + this.selected = 0; + + this._super(me.Renderable, 'init', [closure.x, closure.y, closure.w, closure.h]); + + this.selIcon = new me.Sprite(0, 0, {image: me.loader.getImage('icon_select_30_25')}); + this.selIcon.anchorPoint.set(0, 0); + me.game.world.addChild(this.selIcon, 5); + + this.iconX = this.pos.x; + this.textX = this.iconX + this.selIcon.width * 1.5; + this.centerX = this.pos.x + this.width / 2; + this.incY = 55; + this.updateselected(closure.selected); + }, + + updateselected : function(sel){ + this.selected = sel; + this.selIcon.pos.x = this.iconX; + this.selIcon.pos.y = this.getYForOptionNumber(this.selected) + this.selIcon.height / 2; + }, + update : function (dt) { + return true; + }, + + draw : function (renderer) { + + game.constants.font1.textAlign = "left"; + game.constants.font1.draw(renderer, "PLAY SELECT", this.iconX, this.pos.y); + + for(var i = 0; i console.log("Test Game Started"),(e) => me.state.change("state-splash")); + }, + onDestroyEvent: function() { + + game.manager.onDestroy(); + + me.input.unbindKey(me.input.KEY.LEFT); + me.input.unbindKey(me.input.KEY.RIGHT); + me.input.unbindKey(me.input.KEY.UP); + me.input.unbindKey(me.input.KEY.DOWN); + me.input.unbindKey(me.input.KEY.SPACE); + me.game.world.removeChild(this.HUD); + } +}); diff --git a/Games/Contra-Shooter/lib/melonJS.js b/Games/Contra-Shooter/lib/melonJS.js new file mode 100644 index 0000000000..ed36a6e2bf --- /dev/null +++ b/Games/Contra-Shooter/lib/melonJS.js @@ -0,0 +1,30606 @@ +/** + * melonJS Game Engine v4.1.0 + * http://www.melonjs.org + * @license {@link http://www.opensource.org/licenses/mit-license.php|MIT} + * @copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/* eslint-disable space-before-blocks, no-global-assign, no-native-reassign */ + +(function () { + + /** + * The built in window Object + * @external window + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window.window|window} + */ + + /** + * (m)elonJS (e)ngine : All melonJS functions are defined inside + * of this namespace. + *

You generally should not add new properties to this namespace as it may be + * overwritten in future versions.

+ * @name me + * @namespace + */ + window.me = window.me || {}; + + /* + * DOM loading stuff + */ + var readyBound = false, isReady = false, readyList = []; + + // Handle when the DOM is ready + function domReady() { + // Make sure that the DOM is not already loaded + if (!isReady) { + // be sure document.body is there + if (!document.body) { + return setTimeout(domReady, 13); + } + + // clean up loading event + if (document.removeEventListener) { + document.removeEventListener( + "DOMContentLoaded", + domReady, + false + ); + } + // remove the event on window.onload (always added in `onReady`) + window.removeEventListener("load", domReady, false); + + // execute all callbacks + while (readyList.length){ + readyList.shift().call(window, []); + } + + // Remember that the DOM is ready + isReady = true; + + /* + * Add support for AMD (Asynchronous Module Definition) libraries + * such as require.js. + */ + if (typeof define === "function" && define.amd) { + define("me", [], function () { + return me; + }); + } + } + } + + /** + * Specify a function to execute when the DOM is fully loaded + * @memberOf external:window# + * @alias onReady + * @param {Function} fn A function to execute after the DOM is ready. + * @example + * // small main skeleton + * var game = { + * // Initialize the game + * // called by the window.onReady function + * onload : function () { + * // init video + * if (!me.video.init('screen', 640, 480, true)) { + * alert("Sorry but your browser does not support html 5 canvas."); + * return; + * } + * + * // initialize the "audio" + * me.audio.init("mp3,ogg"); + * + * // set callback for ressources loaded event + * me.loader.onload = this.loaded.bind(this); + * + * // set all ressources to be loaded + * me.loader.preload(game.resources); + * + * // load everything & display a loading screen + * me.state.change(me.state.LOADING); + * }, + * + * // callback when everything is loaded + * loaded : function () { + * // define stuff + * // .... + * + * // change to the menu screen + * me.state.change(me.state.MENU); + * } + * }; // game + * + * // "bootstrap" + * window.onReady(function () { + * game.onload(); + * }); + */ + window.onReady = function (fn) { + // If the DOM is already ready + if (isReady) { + // Execute the function immediately + fn.call(window, []); + } + else { + // Add the function to the wait list + readyList.push(fn); + + // attach listeners if not yet done + if (!readyBound) { + // directly call domReady if document is already "ready" + if (document.readyState === "complete") { + // defer the fn call to ensure our script is fully loaded + window.setTimeout(domReady, 0); + } + else { + if (document.addEventListener) { + // Use the handy event callback + document.addEventListener("DOMContentLoaded", domReady, false); + } + // A fallback to window.onload, that will always work + window.addEventListener("load", domReady, false); + } + readyBound = true; + } + } + }; + + // call the library init function when ready + // (this should not be here?) + if (me.skipAutoInit !== true) { + window.onReady(function () { + me.boot(); + }); + } + else { + /** + * @ignore + */ + me.init = function () { + me.boot(); + domReady(); + }; + } + + if (!window.throttle) { + /** + * a simple throttle function + * use same fct signature as the one in prototype + * in case it's already defined before + * @ignore + */ + window.throttle = function (delay, no_trailing, callback) { + var last = window.performance.now(), deferTimer; + // `no_trailing` defaults to false. + if (typeof no_trailing !== "boolean") { + no_trailing = false; + } + return function () { + var now = window.performance.now(); + var elasped = now - last; + var args = arguments; + if (elasped < delay) { + if (no_trailing === false) { + // hold on to it + clearTimeout(deferTimer); + deferTimer = setTimeout(function () { + last = now; + return callback.apply(null, args); + }, elasped); + } + } + else { + last = now; + return callback.apply(null, args); + } + }; + }; + } + + if (typeof console === "undefined") { + /** + * Dummy console.log to avoid crash + * in case the browser does not support it + * @ignore + */ + console = { // jshint ignore:line + log : function () {}, + info : function () {}, + error : function () { + alert(Array.prototype.slice.call(arguments).join(", ")); + } + }; + } + +})(); +/* eslint-enable space-before-blocks, no-global-assign, no-native-reassign */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/* eslint-disable no-extend-native */ + +/** + * The built in Function Object + * @external Function + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function|Function} + */ +if (!Function.prototype.bind) { + /** @ignore */ + var Empty = function () {}; + + /** + * Binds this function to the given context by wrapping it in another function and returning the wrapper.

+ * Whenever the resulting "bound" function is called, it will call the original ensuring that this is set to context.

+ * Also optionally curries arguments for the function. + * @memberof! external:Function# + * @alias bind + * @param {Object} that the object to bind to. + * @param {} [arguments...] Optional additional arguments to curry for the function. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind|Function.bind} + * @example + * // Ensure that our callback is triggered with the right object context (this): + * myObject.onComplete(this.callback.bind(this)); + */ + Function.prototype.bind = function bind(that) { + // ECMAScript 5 compliant implementation + // http://es5.github.com/#x15.3.4.5 + // from https://github.com/kriskowal/es5-shim + var target = this; + if (typeof target !== "function") { + throw new TypeError("Function.prototype.bind called on incompatible " + target); + } + var args = Array.prototype.slice.call(arguments, 1); + var bound = function () { + if (this instanceof bound) { + var result = target.apply(this, args.concat(Array.prototype.slice.call(arguments))); + if (Object(result) === result) { + return result; + } + return this; + } + else { + return target.apply(that, args.concat(Array.prototype.slice.call(arguments))); + } + }; + if (target.prototype) { + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + return bound; + }; +} + +/** + * Executes a function as soon as the interpreter is idle (stack empty). + * @memberof! external:Function# + * @alias defer + * @param {Object} context The execution context of the deferred function. + * @param {} [arguments...] Optional additional arguments to carry for the + * function. + * @return {Number} id that can be used to clear the deferred function using + * clearTimeout + * @example + * // execute myFunc() when the stack is empty, + * // with the current context and 'myArgument' as parameter + * myFunc.defer(this, 'myArgument'); + */ +Function.prototype.defer = function () { + return setTimeout(this.bind.apply(this, arguments), 0.01); +}; +/* eslint-enable no-extend-native */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * The built in Object object. + * @external Object + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object|Object} + */ + +/* eslint-disable no-self-compare */ + +if (!Object.defineProperty) { + /** + * simple defineProperty function definition (if not supported by the browser)
+ * if defineProperty is redefined, internally use __defineGetter__/__defineSetter__ as fallback + * @param {Object} obj The object on which to define the property. + * @param {string} prop The name of the property to be defined or modified. + * @param {Object} desc The descriptor for the property being defined or modified. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty|Object.defineProperty} + */ + Object.defineProperty = function (obj, prop, desc) { + // check if Object support __defineGetter function + if (obj.__defineGetter__) { + if (desc.get) { + obj.__defineGetter__(prop, desc.get); + } + if (desc.set) { + obj.__defineSetter__(prop, desc.set); + } + } else { + // we should never reach this point.... + throw new TypeError("Object.defineProperty not supported"); + } + }; +} + +if (!Object.create) { + /** + * Prototypal Inheritance Create Helper + * @name create + * @memberOf external:Object# + * @function + * @param {Object} o + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create|Object.create} + * @example + * // declare oldObject + * oldObject = new Object(); + * // make some crazy stuff with oldObject (adding functions, etc...) + * // ... + * + * // make newObject inherits from oldObject + * newObject = Object.create(oldObject); + */ + Object.create = function (o) { + var Fn = function () {}; + Fn.prototype = o; + return new Fn(); + }; +} + +if (!Object.is) { + /** + * The Object.is() method determines whether two values are the same value. + * @name is + * @memberOf external:Object# + * @function + * @param {Object} a The first value to compare + * @param {Object} b The second value to compare + * @return {Boolean} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is|Object.is} + * @example + * // Strings are equal + * var s = "foo"; + * Object.is(s, "foo"); //> true + * + * // 0 and -0 are not equal + * Object.is(0, -0); //>false + * + * // NaN and NaN are equal + * Object.is(NaN, NaN); //> true + * + * // Two object references are not equal + * Object.is({}, {}); //> false + * + * // Two vars referencing one object are equal + * var a = {}, b = a; + * Object.is(a, b); //> true + */ + Object.is = function(a, b) { + // SameValue algorithm + if (a === b) { // Steps 1-5, 7-10 + // Steps 6.b-6.e: +0 != -0 + return a !== 0 || 1 / a === 1 / b; + } else { + // Step 6.a: NaN == NaN + return a !== a && b !== b; + } + }; +} + +if (!Object.assign) { + (function () { + /** + * The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. + * The Object.assign method only copies enumerable and own properties from a source object to a target object. + * It uses [[Get]] on the source and [[Put]] on the target, so it will invoke getters and setters. + * Therefore it assigns properties versus just copying or defining new properties. + * This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. + * For copying propertiy definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor and Object.defineProperty should be used instead. + * @name assign + * @memberOf external:Object# + * @function + * @param {Object} target The target object. + * @param {Object[]} sources The source object(s). + * @return {Object} The target object gets returned. + * @see {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign} + * @example + * // Merging objects + * var o1 = { a: 1 }; + * var o2 = { b: 2 }; + * var o3 = { c: 3 }; + * + * var obj = Object.assign(o1, o2, o3); + * console.log(obj); + * // { a: 1, b: 2, c: 3 } + */ + Object.assign = function (target) { + "use strict"; + // We must check against these specific cases. + if (target === undefined || target === null) { + throw new TypeError("Cannot convert undefined or null to object"); + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output; + }; + })(); +} + +/** + * Extend a class prototype with the provided mixin descriptors. + * Designed as a faster replacement for John Resig's Simple Inheritance. + * @name extend + * @memberOf me.Object + * @function + * @param {Object[]} mixins... Each mixin is a dictionary of functions, or a + * previously extended class whose methods will be applied to the target class + * prototype. + * @return {Object} + * @example + * var Person = me.Object.extend({ + * "init" : function (isDancing) { + * this.dancing = isDancing; + * }, + * "dance" : function () { + * return this.dancing; + * } + * }); + * + * var Ninja = Person.extend({ + * "init" : function () { + * // Call the super constructor, passing a single argument + * Person.prototype.init.apply(this, [false]); + * }, + * "dance" : function () { + * // Call the overridden dance() method + * return Person.prototype.dance.apply(this); + * }, + * "swingSword" : function () { + * return true; + * } + * }); + * + * var Pirate = Person.extend(Ninja, { + * "init" : function () { + * // Call the super constructor, passing a single argument + * Person.prototype.init.apply(this, [true]); + * } + * }); + * + * var p = new Person(true); + * console.log(p.dance()); // => true + * + * var n = new Ninja(); + * console.log(n.dance()); // => false + * console.log(n.swingSword()); // => true + * + * var r = new Pirate(); + * console.log(r.dance()); // => true + * console.log(r.swingSword()); // => true + * + * console.log( + * p instanceof Person && + * n instanceof Ninja && + * n instanceof Person && + * r instanceof Pirate && + * r instanceof Person + * ); // => true + * + * console.log(r instanceof Ninja); // => false + */ +(function () { + function extend() { + var methods = {}; + var mixins = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) { + mixins.push(arguments[i]); + } + + /** + * The class constructor which calls the user `init` constructor. + * @ignore + */ + function Class() { + // Call the user constructor + this.init.apply(this, arguments); + return this; + } + + // Apply superClass + Class.prototype = Object.create(this.prototype); + + // Apply all mixin methods to the class prototype + mixins.forEach(function (mixin) { + apply_methods(Class, methods, mixin.__methods__ || mixin); + }); + + // Verify constructor exists + if (!("init" in Class.prototype)) { + throw new TypeError( + "extend: Class is missing a constructor named `init`" + ); + } + + // Apply syntactic sugar for accessing methods on super classes + Object.defineProperty(Class.prototype, "_super", { + "value" : _super + }); + + // Create a hidden property on the class itself + // List of methods, used for applying classes as mixins + Object.defineProperty(Class, "__methods__", { + "value" : methods + }); + + // Make this class extendable + Class.extend = extend; + + return Class; + } + + /** + * Apply methods to the class prototype. + * @ignore + */ + function apply_methods(Class, methods, descriptor) { + Object.keys(descriptor).forEach(function (method) { + methods[method] = descriptor[method]; + + if (typeof(descriptor[method]) !== "function") { + throw new TypeError( + "extend: Method `" + method + "` is not a function" + ); + } + + Object.defineProperty(Class.prototype, method, { + "configurable" : true, + "value" : descriptor[method] + }); + }); + } + + /** + * Special method that acts as a proxy to the super class. + * @name _super + * @ignore + */ + function _super(superClass, method, args) { + return superClass.prototype[method].apply(this, args); + } + + /** + * The base class from which all jay-extend classes inherit. + * @ignore + */ + var Jay = function () { + Object.apply(this, arguments); + }; + Jay.prototype = Object.create(Object.prototype); + Jay.prototype.constructor = Jay; + + Object.defineProperty(Jay, "extend", { + "value" : extend + }); + + /** + * The base class from which all melonJS objects inherit. + * See: {@link https://github.com/parasyte/jay-extend} + * @class + * @extends external:Object# + * @memberOf me + */ + me.Object = Jay; +})(); +/* eslint-enable no-self-compare */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * The built in Error object. + * @external Error + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error|Error} + */ + +/** + * melonJS base class for exception handling. + * @name Error + * @memberOf me + * @constructor + * @param {String} msg Error message. + */ +me.Error = me.Object.extend.bind(Error)({ + /** + * @ignore + */ + init : function (msg) { + this.name = "me.Error"; + this.message = msg; + } +}); + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + + /** + * The built in Math Object + * @external Math + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math|Math} + */ + +if (!Math.sign) { + /** + * The Math.sign() function returns the sign of a number, indicating whether the number is positive, negative or zero. + * @memberof! external:Math# + * @alias sign + * @param {number} x a number. + * @return {number} sign of the number + */ + Math.sign = function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; +} + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + + /** + * The built in Number Object + * @external Number + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number|Number} + */ + +/* eslint-disable no-extend-native */ + +/** + * add a clamp fn to the Number object + * @memberof! external:Number# + * @alias clamp + * @param {number} low lower limit + * @param {number} high higher limit + * @return {number} clamped value + */ +Number.prototype.clamp = function (low, high) { + return this < low ? low : this > high ? high : +this; +}; + +/** + * return a random integer between min (included) and max (excluded) + * @memberof! external:Number# + * @alias random + * @param {number} [min=this] minimum value. + * @param {number} max maximum value. + * @return {number} random value + * @example + * // Print a random number; one of 5, 6, 7, 8, 9 + * console.log( (5).random(10) ); + * // Select a random array element + * var ar = [ "foo", "bar", "baz" ]; + * console.log(ar[ (0).random(ar.length) ]); + */ +Number.prototype.random = function (min, max) { + if (!max) { + max = min; + min = this; + } + return (~~(Math.random() * (max - min)) + min); +}; + +/** + * return a random float between min, max (exclusive) + * @memberof! external:Number# + * @alias randomFloat + * @param {number} [min=this] minimum value. + * @param {number} max maximum value. + * @return {number} random value + * @example + * // Print a random number; one of 5, 6, 7, 8, 9 + * console.log( (5).random(10) ); + * // Select a random array element + * var ar = [ "foo", "bar", "baz" ]; + * console.log(ar[ (0).random(ar.length) ]); + */ +Number.prototype.randomFloat = function (min, max) { + if (!max) { + max = min; + min = this; + } + return (Math.random() * (max - min)) + min; +}; + +/** + * return a weighted random between min, max (exclusive) + * favoring the lower numbers + * @memberof! external:Number# + * @alias weightedRandom + * @param {number} [min=this] minimum value. + * @param {number} max maximum value. + * @return {number} random value + * @example + * // Print a random number; one of 5, 6, 7, 8, 9 + * console.log( (5).random(10) ); + * // Select a random array element + * var ar = [ "foo", "bar", "baz" ]; + * console.log(ar[ (0).random(ar.length) ]); + */ +Number.prototype.weightedRandom = function (min, max) { + if (!max) { + max = min; + min = this; + } + return (~~(Math.pow(Math.random(), 2) * (max - min)) + min); +}; + +/** + * round a value to the specified number of digit + * @memberof! external:Number# + * @alias round + * @param {number} [num=this] value to be rounded. + * @param {number} dec number of decimal digit to be rounded to. + * @return {number} rounded value + * @example + * // round a specific value to 2 digits + * Number.prototype.round (10.33333, 2); // return 10.33 + * // round a float value to 4 digits + * num = 10.3333333 + * num.round(4); // return 10.3333 + */ +Number.prototype.round = function (num, dec) { + // if only one argument use the object value + num = (arguments.length < 2) ? this : num; + var powres = Math.pow(10, dec || num || 0); + return (~~(0.5 + num * powres) / powres); +}; + +/** + * a quick toHex function
+ * given number must be an int, with a value between 0 and 255 + * @memberof! external:Number# + * @alias toHex + * @return {string} converted hexadecimal value + */ +Number.prototype.toHex = function () { + return "0123456789ABCDEF".charAt((this - (this % 16)) >> 4) + "0123456789ABCDEF".charAt(this % 16); +}; + +/** + * Converts an angle in degrees to an angle in radians + * @memberof! external:Number# + * @alias degToRad + * @param {number} [angle="angle"] angle in degrees + * @return {number} corresponding angle in radians + * @example + * // convert a specific angle + * Number.prototype.degToRad (60); // return 1.0471... + * // convert object value + * var num = 60 + * num.degToRad(); // return 1.0471... + */ +Number.prototype.degToRad = function (angle) { + return (angle || this) / 180.0 * Math.PI; +}; + +/** + * Converts an angle in radians to an angle in degrees. + * @memberof! external:Number# + * @alias radToDeg + * @param {number} [angle="angle"] angle in radians + * @return {number} corresponding angle in degrees + * @example + * // convert a specific angle + * Number.prototype.radToDeg (1.0471975511965976); // return 59.9999... + * // convert object value + * num = 1.0471975511965976 + * Math.ceil(num.radToDeg()); // return 60 + */ +Number.prototype.radToDeg = function (angle) { + return (angle || this) * (180.0 / Math.PI); +}; +/* eslint-enable no-extend-native */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * The built in String Object + * @external String + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String|String} + */ + +/* eslint-disable no-extend-native, yoda */ + +if (!String.prototype.trim) { + if (!String.prototype.trim) { + (function() { + // Make sure we trim BOM and NBSP + var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + /** + * removes whitespace from both ends of a string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). + * @memberof! external:String# + * @alias trim + * @return {string} the string stripped of whitespace from both ends. + */ + String.prototype.trim = function() { + return this.replace(rtrim, ""); + }; + })(); + } +} + +if (!String.prototype.trimLeft) { + /** + * returns the string stripped of whitespace from the left. + * @memberof! external:String# + * @alias trimLeft + * @return {string} trimmed string + */ + String.prototype.trimLeft = function () { + return this.replace(/^\s+/, ""); + }; +} + +if (!String.prototype.trimRight) { + /** + * returns the string stripped of whitespace from the right. + * @memberof! external:String# + * @alias trimRight + * @return {string} trimmed string + */ + String.prototype.trimRight = function () { + return this.replace(/\s+$/, ""); + }; +} + +/** + * add isNumeric fn to the string object + * @memberof! external:String# + * @alias isNumeric + * @return {boolean} true if string contains only digits + */ +String.prototype.isNumeric = function () { + return (!isNaN(this) && this.trim() !== ""); +}; + +/** + * add a isBoolean fn to the string object + * @memberof! external:String# + * @alias isBoolean + * @return {boolean} true if the string is either true or false + */ +String.prototype.isBoolean = function () { + var trimmed = this.trim(); + return ("true" === trimmed) || ("false" === trimmed); +}; + +if (!String.prototype.includes) { + /** + * determines whether one string may be found within another string. + * @memberof! external:String# + * @alias includes + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes + * @param {string} searchString A string to be searched for within this string. + * @param {number} [position=0] The position in this string at which to begin searching for the given string. + * @return {boolean} true if contains the specified string + */ + String.prototype.includes = function() { + return String.prototype.indexOf.apply(this, arguments) !== -1; + }; +} + +/** + * convert the string to hex value + * @memberof! external:String# + * @alias toHex + * @return {string} + */ +String.prototype.toHex = function () { + var res = "", c = 0; + while (c < this.length) { + res += this.charCodeAt(c++).toString(16); + } + return res; +}; +/* eslint-enable no-extend-native */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * The built in Array Object + * @external Array + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array|Array} + */ + +/* eslint-disable no-extend-native */ + +/** + * Remove the specified object from the Array
+ * @memberof! external:Array# + * @alias remove + * @param {Object} object to be removed + */ +Array.prototype.remove = function (obj) { + var i = Array.prototype.indexOf.call(this, obj); + if (i !== -1) { + Array.prototype.splice.call(this, i, 1); + } + return this; +}; + +if (!Array.prototype.forEach) { + /** + * provide a replacement for browsers that don't + * support Array.prototype.forEach (JS 1.6) + * @ignore + */ + Array.prototype.forEach = function (callback, scope) { + for (var i = 0, j = this.length; j--; i++) { + callback.call(scope || this, this[i], i, this); + } + }; +} + +if (!Array.isArray) { + /** + * provide a replacement for browsers that don't + * natively support Array.isArray + * @ignore + */ + Array.isArray = function (vArg) { + var isArray; + isArray = vArg instanceof Array; + return isArray; + }; +} + +/** + * return a random array element + * @memberof! external:Array# + * @alias random + * @param {array} entry array to pick a element + * @return {any} random member of array + */ +Array.prototype.random = function (entry) { + return entry[(0).random(entry.length)]; +}; + +/** + * return a weighted random array element, favoring the earlier entries + * @memberof! external:Array# + * @alias weightedRandom + * @param {array} entry array to pick a element + * @return {any} random member of array + */ +Array.prototype.weightedRandom = function (entry) { + return entry[(0).weightedRandom(entry.length)]; +}; + +/** + * A fake TypedArray object to be used for the TypedArray polyfills + * @ignore + */ +me.TypedArray = function (a) { + var i = 0; + if (Array.isArray(a)) { + this.concat(a.slice()); + } + else if ((arguments.length === 1) && (typeof(a) === "number")) { + for (i = 0; i < a; i++) { + this.push(0); + } + } + else { + throw new me.Error( + "TypedArray polyfill: Unsupported constructor arguments", + arguments + ); + } +}; +me.TypedArray.prototype = Array.prototype; + +/** + * The set() method stores multiple values in the typed array, reading input values from a specified array. + * @ignore + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set|TypedArray.prototype.set} + */ +me.TypedArray.prototype.set = function (source, offset) { + offset = offset || 0; + + if (source.length + offset > this.length) { + throw new me.Error( + "TypedArray pollyfill: Buffer overflow in set" + ); + } + + for (var i = 0; i < source.length; i++, offset++) { + this[offset] = source[i]; + } +}; + +/** + * The built in Float32Array object. + * @external Float32Array + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Float32Array|Float32Array} + */ +window.Float32Array = window.Float32Array || me.TypedArray; + +/** + * The built in Uint8Array object. + * @external Uint8Array + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Uint8Array|Uint8Array} + */ +window.Uint8Array = window.Uint8Array || me.TypedArray; + +/** + * The built in Uint16Array object. + * @external Uint16Array + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Uint16Array|Uint16Array} + */ +window.Uint16Array = window.Uint16Array || me.TypedArray; + +/** + * The built in Uint32Array object. + * @external Uint32Array + * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Uint32Array|Uint32Array} + */ +window.Uint32Array = window.Uint32Array || me.TypedArray; +/* eslint-enable no-extend-native */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +// define window.performance if undefined +if (typeof window.performance === "undefined") { + window.performance = {}; +} + +if (typeof Date.now === "undefined") { + /** + * provide a replacement for browser not + * supporting Date.now (JS 1.5) + * @ignore + */ + Date.now = function () { + return new Date().getTime(); + }; +} + +if (!window.performance.now) { + var timeOffset = Date.now(); + + if (window.performance.timing && + window.performance.timing.navigationStart) { + timeOffset = window.performance.timing.navigationStart; + } + /** + * provide a polyfill for window.performance now + * to provide consistent time information across browser + * (always return the elapsed time since the browser started) + * @ignore + */ + window.performance.now = function () { + return Date.now() - timeOffset; + }; +} + +/* + +Copyright (C) 2011 by Andrea Giammarchi, @WebReflection + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* eslint-disable space-before-blocks, quotes, block-spacing, new-parens, comma-spacing, yoda, curly, no-self-compare, pace-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new, eqeqeq */ + +(function (exports) {'use strict'; + //shared pointer + var i; + //shortcuts + var defineProperty = Object.defineProperty, is = Object.is; + + + //Polyfill global objects + if (typeof WeakMap == 'undefined') { + exports.WeakMap = createCollection({ + // WeakMap#delete(key:void*):boolean + 'delete': sharedDelete, + // WeakMap#clear(): + clear: sharedClear, + // WeakMap#get(key:void*):void* + get: sharedGet, + // WeakMap#has(key:void*):boolean + has: mapHas, + // WeakMap#set(key:void*, value:void*):void + set: sharedSet + }, true); + } + + if (typeof Map == 'undefined' || typeof ((new Map).values) !== 'function' || !(new Map).values().next) { + exports.Map = createCollection({ + // WeakMap#delete(key:void*):boolean + 'delete': sharedDelete, + //:was Map#get(key:void*[, d3fault:void*]):void* + // Map#has(key:void*):boolean + has: mapHas, + // Map#get(key:void*):boolean + get: sharedGet, + // Map#set(key:void*, value:void*):void + set: sharedSet, + // Map#keys(void):Iterator + keys: sharedKeys, + // Map#values(void):Iterator + values: sharedValues, + // Map#entries(void):Iterator + entries: mapEntries, + // Map#forEach(callback:Function, context:void*):void ==> callback.call(context, key, value, mapObject) === not in specs` + forEach: sharedForEach, + // Map#clear(): + clear: sharedClear + }); + } + + if (typeof Set == 'undefined' || typeof ((new Set).values) !== 'function' || !(new Set).values().next) { + exports.Set = createCollection({ + // Set#has(value:void*):boolean + has: setHas, + // Set#add(value:void*):boolean + add: sharedAdd, + // Set#delete(key:void*):boolean + 'delete': sharedDelete, + // Set#clear(): + clear: sharedClear, + // Set#keys(void):Iterator + keys: sharedValues, // specs actually say "the same function object as the initial value of the values property" + // Set#values(void):Iterator + values: sharedValues, + // Set#entries(void):Iterator + entries: setEntries, + // Set#forEach(callback:Function, context:void*):void ==> callback.call(context, value, index) === not in specs + forEach: sharedForEach + }); + } + + if (typeof WeakSet == 'undefined') { + exports.WeakSet = createCollection({ + // WeakSet#delete(key:void*):boolean + 'delete': sharedDelete, + // WeakSet#add(value:void*):boolean + add: sharedAdd, + // WeakSet#clear(): + clear: sharedClear, + // WeakSet#has(value:void*):boolean + has: setHas + }, true); + } + + + /** + * ES6 collection constructor + * @return {Function} a collection class + */ + function createCollection(proto, objectOnly){ + function Collection(a){ + if (!this || this.constructor !== Collection) return new Collection(a); + this._keys = []; + this._values = []; + this._itp = []; // iteration pointers + this.objectOnly = objectOnly; + + //parse initial iterable argument passed + if (a) init.call(this, a); + } + + //define size for non object-only collections + if (!objectOnly) { + defineProperty(proto, 'size', { + get: sharedSize + }); + } + + //set prototype + proto.constructor = Collection; + Collection.prototype = proto; + + return Collection; + } + + + /** parse initial iterable argument passed */ + function init(a){ + //init Set argument, like `[1,2,3,{}]` + if (this.add) + a.forEach(this.add, this); + //init Map argument like `[[1,2], [{}, 4]]` + else + a.forEach(function(a){this.set(a[0],a[1]);}, this); + } + + + /** delete */ + function sharedDelete(key) { + if (this.has(key)) { + this._keys.splice(i, 1); + this._values.splice(i, 1); + // update iteration pointers + this._itp.forEach(function(p) { if (i < p[0]) p[0]--; }); + } + // Aurora here does it while Canary doesn't + return -1 < i; + } + + function sharedGet(key) { + return this.has(key) ? this._values[i] : undefined; + } + + function has(list, key) { + if (this.objectOnly && key !== Object(key)) + throw new TypeError("Invalid value used as weak collection key"); + //NaN or 0 passed + if (key != key || key === 0) for (i = list.length; i-- && !is(list[i], key);){} + else i = list.indexOf(key); + return -1 < i; + } + + function setHas(value) { + return has.call(this, this._values, value); + } + + function mapHas(value) { + return has.call(this, this._keys, value); + } + + /** @chainable */ + function sharedSet(key, value) { + this.has(key) ? + this._values[i] = value + : + this._values[this._keys.push(key) - 1] = value + ; + return this; + } + + /** @chainable */ + function sharedAdd(value) { + if (!this.has(value)) this._values.push(value); + return this; + } + + function sharedClear() { + (this._keys || 0).length = + this._values.length = 0; + } + + /** keys, values, and iterate related methods */ + function sharedKeys() { + return sharedIterator(this._itp, this._keys); + } + + function sharedValues() { + return sharedIterator(this._itp, this._values); + } + + function mapEntries() { + return sharedIterator(this._itp, this._keys, this._values); + } + + function setEntries() { + return sharedIterator(this._itp, this._values, this._values); + } + + function sharedIterator(itp, array, array2) { + var p = [0], done = false; + itp.push(p); + return { + next: function() { + var v, k = p[0]; + if (!done && k < array.length) { + v = array2 ? [array[k], array2[k]]: array[k]; + p[0]++; + } else { + done = true; + itp.splice(itp.indexOf(p), 1); + } + return { done: done, value: v }; + } + }; + } + + function sharedSize() { + return this._values.length; + } + + function sharedForEach(callback, context) { + var it = this.entries(); + for (;;) { + var r = it.next(); + if (r.done) break; + callback.call(context, r.value[1], r.value[0], this); + } + } + +})(typeof exports != 'undefined' && typeof global != 'undefined' ? global : window ); +/* eslint-enable space-before-blocks, quotes, block-spacing, new-parens, comma-spacing, yoda, curly, no-self-compare, pace-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new, eqeqeq */ + +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://github.com/stefanpenner/es6-promise + * @version 3.2.1 + */ + +/* eslint-disable no-unused-vars, no-new-func, space-before-blocks, quotes, space-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new*/ + +(function() { + "use strict"; + function lib$es6$promise$utils$$objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); + } + + function lib$es6$promise$utils$$isFunction(x) { + return typeof x === 'function'; + } + + function lib$es6$promise$utils$$isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + + var lib$es6$promise$utils$$_isArray; + if (!Array.isArray) { + lib$es6$promise$utils$$_isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + lib$es6$promise$utils$$_isArray = Array.isArray; + } + + var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; + var lib$es6$promise$asap$$len = 0; + var lib$es6$promise$asap$$vertxNext; + var lib$es6$promise$asap$$customSchedulerFn; + + var lib$es6$promise$asap$$asap = function asap(callback, arg) { + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; + lib$es6$promise$asap$$len += 2; + if (lib$es6$promise$asap$$len === 2) { + // If len is 2, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + if (lib$es6$promise$asap$$customSchedulerFn) { + lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); + } else { + lib$es6$promise$asap$$scheduleFlush(); + } + } + } + + function lib$es6$promise$asap$$setScheduler(scheduleFn) { + lib$es6$promise$asap$$customSchedulerFn = scheduleFn; + } + + function lib$es6$promise$asap$$setAsap(asapFn) { + lib$es6$promise$asap$$asap = asapFn; + } + + var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; + var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; + var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; + var lib$es6$promise$asap$$isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; + + // test for web worker but not in IE10 + var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; + + // node + function lib$es6$promise$asap$$useNextTick() { + // node version 0.10.x displays a deprecation warning when nextTick is used recursively + // see https://github.com/cujojs/when/issues/410 for details + return function() { + process.nextTick(lib$es6$promise$asap$$flush); + }; + } + + // vertx + function lib$es6$promise$asap$$useVertxTimer() { + return function() { + lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); + }; + } + + function lib$es6$promise$asap$$useMutationObserver() { + var iterations = 0; + var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function() { + node.data = (iterations = ++iterations % 2); + }; + } + + // web worker + function lib$es6$promise$asap$$useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = lib$es6$promise$asap$$flush; + return function () { + channel.port2.postMessage(0); + }; + } + + function lib$es6$promise$asap$$useSetTimeout() { + return function() { + setTimeout(lib$es6$promise$asap$$flush, 1); + }; + } + + var lib$es6$promise$asap$$queue = new Array(1000); + function lib$es6$promise$asap$$flush() { + for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { + var callback = lib$es6$promise$asap$$queue[i]; + var arg = lib$es6$promise$asap$$queue[i+1]; + + callback(arg); + + lib$es6$promise$asap$$queue[i] = undefined; + lib$es6$promise$asap$$queue[i+1] = undefined; + } + + lib$es6$promise$asap$$len = 0; + } + + function lib$es6$promise$asap$$attemptVertx() { + try { + var r = require; + var vertx = r('vertx'); + lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; + return lib$es6$promise$asap$$useVertxTimer(); + } catch(e) { + return lib$es6$promise$asap$$useSetTimeout(); + } + } + + var lib$es6$promise$asap$$scheduleFlush; + // Decide what async method to use to triggering processing of queued callbacks: + if (lib$es6$promise$asap$$isNode) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); + } else if (lib$es6$promise$asap$$BrowserMutationObserver) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); + } else if (lib$es6$promise$asap$$isWorker) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); + } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx(); + } else { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); + } + function lib$es6$promise$then$$then(onFulfillment, onRejection) { + var parent = this; + + var child = new this.constructor(lib$es6$promise$$internal$$noop); + + if (child[lib$es6$promise$$internal$$PROMISE_ID] === undefined) { + lib$es6$promise$$internal$$makePromise(child); + } + + var state = parent._state; + + if (state) { + var callback = arguments[state - 1]; + lib$es6$promise$asap$$asap(function(){ + lib$es6$promise$$internal$$invokeCallback(state, child, callback, parent._result); + }); + } else { + lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + } + var lib$es6$promise$then$$default = lib$es6$promise$then$$then; + function lib$es6$promise$promise$resolve$$resolve(object) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$resolve(promise, object); + return promise; + } + var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; + var lib$es6$promise$$internal$$PROMISE_ID = Math.random().toString(36).substring(16); + + function lib$es6$promise$$internal$$noop() {} + + var lib$es6$promise$$internal$$PENDING = void 0; + var lib$es6$promise$$internal$$FULFILLED = 1; + var lib$es6$promise$$internal$$REJECTED = 2; + + var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$selfFulfillment() { + return new TypeError("You cannot resolve a promise with itself"); + } + + function lib$es6$promise$$internal$$cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.'); + } + + function lib$es6$promise$$internal$$getThen(promise) { + try { + return promise.then; + } catch(error) { + lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; + return lib$es6$promise$$internal$$GET_THEN_ERROR; + } + } + + function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } + } + + function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { + lib$es6$promise$asap$$asap(function(promise) { + var sealed = false; + var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + lib$es6$promise$$internal$$resolve(promise, value); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + lib$es6$promise$$internal$$reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + lib$es6$promise$$internal$$reject(promise, error); + } + }, promise); + } + + function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { + if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, thenable._result); + } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, thenable._result); + } else { + lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { + lib$es6$promise$$internal$$resolve(promise, value); + }, function(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } + } + + function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable, then) { + if (maybeThenable.constructor === promise.constructor && + then === lib$es6$promise$then$$default && + constructor.resolve === lib$es6$promise$promise$resolve$$default) { + lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); + } else { + if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); + } else if (then === undefined) { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } else if (lib$es6$promise$utils$$isFunction(then)) { + lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); + } else { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } + } + } + + function lib$es6$promise$$internal$$resolve(promise, value) { + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment()); + } else if (lib$es6$promise$utils$$objectOrFunction(value)) { + lib$es6$promise$$internal$$handleMaybeThenable(promise, value, lib$es6$promise$$internal$$getThen(value)); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + } + + function lib$es6$promise$$internal$$publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + lib$es6$promise$$internal$$publish(promise); + } + + function lib$es6$promise$$internal$$fulfill(promise, value) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + + promise._result = value; + promise._state = lib$es6$promise$$internal$$FULFILLED; + + if (promise._subscribers.length !== 0) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); + } + } + + function lib$es6$promise$$internal$$reject(promise, reason) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + promise._state = lib$es6$promise$$internal$$REJECTED; + promise._result = reason; + + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); + } + + function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; + subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; + + if (length === 0 && parent._state) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); + } + } + + function lib$es6$promise$$internal$$publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; + } + + function lib$es6$promise$$internal$$ErrorObject() { + this.error = null; + } + + var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; + return lib$es6$promise$$internal$$TRY_CATCH_ERROR; + } + } + + function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { + var hasCallback = lib$es6$promise$utils$$isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = lib$es6$promise$$internal$$tryCatch(callback, detail); + + if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); + return; + } + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== lib$es6$promise$$internal$$PENDING) { + // noop + } else if (hasCallback && succeeded) { + lib$es6$promise$$internal$$resolve(promise, value); + } else if (failed) { + lib$es6$promise$$internal$$reject(promise, error); + } else if (settled === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, value); + } else if (settled === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } + } + + function lib$es6$promise$$internal$$initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + lib$es6$promise$$internal$$resolve(promise, value); + }, function rejectPromise(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } catch(e) { + lib$es6$promise$$internal$$reject(promise, e); + } + } + + var lib$es6$promise$$internal$$id = 0; + function lib$es6$promise$$internal$$nextId() { + return lib$es6$promise$$internal$$id++; + } + + function lib$es6$promise$$internal$$makePromise(promise) { + promise[lib$es6$promise$$internal$$PROMISE_ID] = lib$es6$promise$$internal$$id++; + promise._state = undefined; + promise._result = undefined; + promise._subscribers = []; + } + + function lib$es6$promise$promise$all$$all(entries) { + return new lib$es6$promise$enumerator$$default(this, entries).promise; + } + var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; + function lib$es6$promise$promise$race$$race(entries) { + /*jshint validthis:true */ + var Constructor = this; + + if (!lib$es6$promise$utils$$isArray(entries)) { + return new Constructor(function(resolve, reject) { + reject(new TypeError('You must pass an array to race.')); + }); + } else { + return new Constructor(function(resolve, reject) { + var length = entries.length; + for (var i = 0; i < length; i++) { + Constructor.resolve(entries[i]).then(resolve, reject); + } + }); + } + } + var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; + function lib$es6$promise$promise$reject$$reject(reason) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$reject(promise, reason); + return promise; + } + var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; + + + function lib$es6$promise$promise$$needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + + function lib$es6$promise$promise$$needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise's eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor + */ + function lib$es6$promise$promise$$Promise(resolver) { + this[lib$es6$promise$$internal$$PROMISE_ID] = lib$es6$promise$$internal$$nextId(); + this._result = this._state = undefined; + this._subscribers = []; + + if (lib$es6$promise$$internal$$noop !== resolver) { + typeof resolver !== 'function' && lib$es6$promise$promise$$needsResolver(); + this instanceof lib$es6$promise$promise$$Promise ? lib$es6$promise$$internal$$initializePromise(this, resolver) : lib$es6$promise$promise$$needsNew(); + } + } + + lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; + lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; + lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; + lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; + lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; + lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; + lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; + + lib$es6$promise$promise$$Promise.prototype = { + constructor: lib$es6$promise$promise$$Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + @ignore + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: lib$es6$promise$then$$default, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + @ignore + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection) { + return this.then(null, onRejection); + } + }; + var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; + function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { + this._instanceConstructor = Constructor; + this.promise = new Constructor(lib$es6$promise$$internal$$noop); + + if (!this.promise[lib$es6$promise$$internal$$PROMISE_ID]) { + lib$es6$promise$$internal$$makePromise(this.promise); + } + + if (Array.isArray(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._result = new Array(this.length); + + if (this.length === 0) { + lib$es6$promise$$internal$$fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + lib$es6$promise$$internal$$fulfill(this.promise, this._result); + } + } + } else { + lib$es6$promise$$internal$$reject(this.promise, lib$es6$promise$enumerator$$validationError()); + } + } + + function lib$es6$promise$enumerator$$validationError() { + return new Error('Array Methods must be provided an Array'); + } + + lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { + var length = this.length; + var input = this._input; + + for (var i = 0; this._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { + this._eachEntry(input[i], i); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { + var c = this._instanceConstructor; + var resolve = c.resolve; + + if (resolve === lib$es6$promise$promise$resolve$$default) { + var then = lib$es6$promise$$internal$$getThen(entry); + + if (then === lib$es6$promise$then$$default && + entry._state !== lib$es6$promise$$internal$$PENDING) { + this._settledAt(entry._state, i, entry._result); + } else if (typeof then !== 'function') { + this._remaining--; + this._result[i] = entry; + } else if (c === lib$es6$promise$promise$$default) { + var promise = new c(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$handleMaybeThenable(promise, entry, then); + this._willSettleAt(promise, i); + } else { + this._willSettleAt(new c(function(resolve) { resolve(entry); }), i); + } + } else { + this._willSettleAt(resolve(entry), i); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { + var promise = this.promise; + + if (promise._state === lib$es6$promise$$internal$$PENDING) { + this._remaining--; + + if (state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } else { + this._result[i] = value; + } + } + + if (this._remaining === 0) { + lib$es6$promise$$internal$$fulfill(promise, this._result); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { + enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); + }); + }; + function lib$es6$promise$polyfill$$polyfill() { + var local; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { + return; + } + + local.Promise = lib$es6$promise$promise$$default; + } + var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; + + var lib$es6$promise$umd$$ES6Promise = { + 'Promise': lib$es6$promise$promise$$default, + 'polyfill': lib$es6$promise$polyfill$$default + }; + + /* global define:true module:true window: true */ + if (typeof define === 'function' && define['amd']) { + define(function() { return lib$es6$promise$umd$$ES6Promise; }); + } else if (typeof module !== 'undefined' && module['exports']) { + module['exports'] = lib$es6$promise$umd$$ES6Promise; + } else if (typeof this !== 'undefined') { + this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; + } + + lib$es6$promise$polyfill$$default(); +}).call(this); +/* eslint-enable no-unused-vars, no-new-func, space-before-blocks, quotes, space-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new */ + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +(function () { + + /** + * me global references + * @ignore + */ + me.mod = "melonJS"; + me.version = "4.1.0"; + /** + * global system settings and browser capabilities + * @namespace + */ + me.sys = { + + /* + * Global settings + */ + + /** + * Set game FPS limiting + * @see me.timer.tick + * @type {Number} + * @default 60 + * @memberOf me.sys + */ + fps : 60, + + /** + * Rate at which the game updates;
+ * must be equal to or lower than the fps + * @see me.timer.tick + * @type {Number} + * @default 60 + * @memberOf me.sys + */ + updatesPerSecond : 60, + + /** + * Enable/disable frame interpolation + * @see me.timer.tick + * @type {Boolean} + * @default false + * @memberOf me.sys + */ + interpolation : false, + + /** + * Global scaling factor + * @type {me.Vector2d} + * @default <0,0> + * @memberOf me.sys + */ + scale : null, //initialized by me.video.init + + /** + * Global gravity settings
+ * will override entities init value if defined
+ * @type {Number|undefined} + * @default undefined + * @memberOf me.sys + */ + gravity : undefined, + + /** + * Specify either to stop on audio loading error or not
+ * if true, melonJS will throw an exception and stop loading
+ * if false, melonJS will disable sounds and output a warning message + * in the console
+ * @type {Boolean} + * @default true + * @memberOf me.sys + */ + stopOnAudioError : true, + + /** + * Specify whether to pause the game when losing focus.
+ * @type {Boolean} + * @default true + * @memberOf me.sys + */ + pauseOnBlur : true, + + /** + * Specify whether to unpause the game when gaining focus.
+ * @type {Boolean} + * @default true + * @memberOf me.sys + */ + resumeOnFocus : true, + + /** + * Specify whether to stop the game when losing focus or not
+ * The engine restarts on focus if this is enabled. + * @type {boolean} + * @default false + * @memberOf me.sys + */ + stopOnBlur : false, + + /** + * Specify the rendering method for layers
+ * if false, visible part of the layers are rendered dynamically
+ * if true, the entire layers are first rendered into an offscreen + * canvas
+ * the "best" rendering method depends of your game
+ * (amount of layer, layer size, amount of tiles per layer, etc.)
+ * note : rendering method is also configurable per layer by adding this + * property to your layer (in Tiled)
+ * @type {Boolean} + * @default false + * @memberOf me.sys + */ + preRender : false, + + /* + * System methods + */ + + /** + * Compare two version strings + * @public + * @function + * @param {String} first First version string to compare + * @param {String} [second="4.1.0"] Second version string to compare + * @return {Number} comparison result
< 0 : first < second
+ * 0 : first == second
+ * > 0 : first > second + * @example + * if (me.sys.checkVersion("4.1.0") > 0) { + * console.error( + * "melonJS is too old. Expected: 4.1.0, Got: " + me.version + * ); + * } + */ + checkVersion : function (first, second) { + second = second || me.version; + + var a = first.split("."); + var b = second.split("."); + var len = Math.min(a.length, b.length); + var result = 0; + + for (var i = 0; i < len; i++) { + if ((result = +a[i] - +b[i])) { + break; + } + } + + return result ? result : a.length - b.length; + } + }; + + function parseHash() { + var hash = {}; + + if (document.location.hash) { + document.location.hash.substr(1).split("&").filter(function (value) { + return (value !== ""); + }).forEach(function (value) { + var kv = value.split("="); + var k = kv.shift(); + var v = kv.join("="); + hash[k] = v || true; + }); + } + + return hash; + } + + // a flag to know if melonJS + // is initialized + var me_initialized = false; + + Object.defineProperty(me, "initialized", { + /** + * @ignore + */ + get : function get() { + return me_initialized; + } + }); + + + /** + * initial boot function + * @ignore + */ + me.boot = function () { + // don't do anything if already initialized (should not happen anyway) + if (me_initialized) { + return; + } + + // check the device capabilites + me.device._check(); + + // initialize me.save + me.save._init(); + + // parse optional url parameters/tags + me.game.HASH = parseHash(); + + // enable/disable the cache + me.loader.setNocache( + me.game.HASH.nocache || false + ); + + // init the FPS counter if needed + me.timer.init(); + + // init the App Manager + me.state.init(); + + // init the Entity Pool + me.pool.init(); + + // automatically enable keyboard events if on desktop + if (me.device.isMobile === false) { + me.input._enableKeyboardEvent(); + } + + // init the level Director + me.levelDirector.reset(); + + me_initialized = true; + }; + +})(); + +/** + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +(function () { + + /** + * me.game represents your current game, it contains all the objects, + * tilemap layers, current viewport, collision map, etc...
+ * me.game is also responsible for updating (each frame) the object status + * and draw them
+ * @namespace me.game + * @memberOf me + */ + me.game = (function () { + // hold public stuff in our singleton + var api = {}; + + /* + * PRIVATE STUFF + */ + + // flag to redraw the sprites + var initialized = false; + + // to know when we have to refresh the display + var isDirty = true; + + // always refresh the display when updatesPerSecond are lower than fps + var isAlwaysDirty = false; + + // frame counter for frameSkipping + // reset the frame counter + var frameCounter = 0; + var frameRate = 1; + + // time accumulation for multiple update calls + var accumulator = 0.0; + var accumulatorMax = 0.0; + var accumulatorUpdateDelta = 0; + + // min update step size + var stepSize = 1000 / 60; + var updateDelta = 0; + var lastUpdateStart = null; + var updateAverageDelta = 0; + + // reference to the renderer object + var renderer = null; + + /* + * PUBLIC STUFF + */ + + /** + * a reference to the game viewport. + * @public + * @type {me.Viewport} + * @name viewport + * @memberOf me.game + */ + api.viewport = null; + + /** + * a reference to the game world
+ * a world is a virtual environment containing all the game objects + * @public + * @type {me.Container} + * @name world + * @memberOf me.game + */ + api.world = null; + + /** + * when true, all objects will be added under the root world container
+ * when false, a `me.Container` object will be created for each + * corresponding `TMXObjectGroup` + * default value : true + * @public + * @type {boolean} + * @name mergeGroup + * @memberOf me.game + */ + api.mergeGroup = true; + + /** + * The property of should be used when sorting entities
+ * value : "x", "y", "z" (default: "z") + * @public + * @type {string} + * @name sortOn + * @memberOf me.game + */ + api.sortOn = "z"; + + /** + * Fired when a level is fully loaded and
+ * and all entities instantiated.
+ * Additionnaly the level id will also be passed + * to the called function. + * @public + * @function + * @name onLevelLoaded + * @memberOf me.game + * @example + * // call myFunction () everytime a level is loaded + * me.game.onLevelLoaded = this.myFunction.bind(this); + */ + api.onLevelLoaded = function () {}; + + /** + * Provide an object hash with all tag parameters specified in the url. + * @property {Boolean} [hitbox=false] draw the hitbox in the debug panel (if enabled) + * @property {Boolean} [velocity=false] draw the entities velocity in the debug panel (if enabled) + * @property {Boolean} [quadtree=false] draw the quadtree in the debug panel (if enabled) + * @property {Boolean} [webgl=false] force the renderer to WebGL + * @public + * @type {Object} + * @name HASH + * @memberOf me.game + * @example + * // http://www.example.com/index.html#debug&hitbox=true&mytag=value + * console.log(me.game.HASH["mytag"]); //> "value" + */ + api.HASH = null; + + /** + * Initialize the game manager + * @name init + * @memberOf me.game + * @private + * @ignore + * @function + * @param {Number} [width] width of the canvas + * @param {Number} [height] width of the canvas + * init function. + */ + api.init = function (width, height) { + if (!initialized) { + // if no parameter specified use the system size + width = width || me.video.renderer.getWidth(); + height = height || me.video.renderer.getHeight(); + + // create a defaut viewport of the same size + api.viewport = new me.Viewport(0, 0, width, height); + + // the root object of our world is an entity container + api.world = new me.Container(0, 0, width, height); + api.world.name = "rootContainer"; + api.world._root = true; + + // initialize the collision system (the quadTree mostly) + me.collision.init(); + + renderer = me.video.renderer; + + // publish init notification + me.event.publish(me.event.GAME_INIT); + + // translate global pointer events + me.input._translatePointerEvents(); + + // make display dirty by default + isDirty = true; + + // set as initialized + initialized = true; + } + }; + + /** + * reset the game Object manager
+ * destroy all current objects + * @name reset + * @memberOf me.game + * @public + * @function + */ + api.reset = function () { + + // clear the quadtree + me.collision.quadTree.clear(); + + // remove all objects + api.world.destroy(); + + // reset the viewport to zero ? + if (api.viewport) { + api.viewport.reset(); + } + + // reset the renderer + renderer.reset(); + + // publish reset notification + me.event.publish(me.event.GAME_RESET); + + // Refresh internal variables for framerate limiting + api.updateFrameRate(); + }; + + /** + * Update the renderer framerate using the system config variables. + * @name updateFrameRate + * @memberOf me.game + * @public + * @function + * @see me.sys.fps + * @see me.sys.updatesPerSecond + */ + api.updateFrameRate = function () { + // reset the frame counter + frameCounter = 0; + frameRate = ~~(0.5 + 60 / me.sys.fps); + + // set step size based on the updatesPerSecond + stepSize = (1000 / me.sys.updatesPerSecond); + accumulator = 0.0; + accumulatorMax = stepSize * 10; + + // display should always re-draw when update speed doesn't match fps + // this means the user intends to write position prediction drawing logic + isAlwaysDirty = (me.sys.fps > me.sys.updatesPerSecond); + }; + + /** + * Returns the parent container of the specified Child in the game world + * @name getParentContainer + * @memberOf me.game + * @function + * @param {me.Renderable} child + * @return {me.Container} + */ + api.getParentContainer = function (child) { + return child.ancestor; + }; + + /** + * force the redraw (not update) of all objects + * @name repaint + * @memberOf me.game + * @public + * @function + */ + + api.repaint = function () { + isDirty = true; + }; + + + /** + * update all objects of the game manager + * @name update + * @memberOf me.game + * @private + * @ignore + * @function + * @param {Number} time current timestamp as provided by the RAF callback + */ + api.update = function (time) { + // handle frame skipping if required + if ((++frameCounter % frameRate) === 0) { + // reset the frame counter + frameCounter = 0; + + // update the timer + me.timer.update(time); + + // update the gamepads + me.input._updateGamepads(); + + accumulator += me.timer.getDelta(); + accumulator = Math.min(accumulator, accumulatorMax); + + updateDelta = (me.sys.interpolation) ? me.timer.getDelta() : stepSize; + accumulatorUpdateDelta = (me.sys.interpolation) ? updateDelta : Math.max(updateDelta, updateAverageDelta); + + while (accumulator >= accumulatorUpdateDelta || me.sys.interpolation) { + lastUpdateStart = window.performance.now(); + + // clear the quadtree + me.collision.quadTree.clear(); + + // insert the world container (children) into the quadtree + me.collision.quadTree.insertContainer(api.world); + + // update all objects (and pass the elapsed time since last frame) + isDirty = api.world.update(updateDelta) || isDirty; + + // update the camera/viewport + isDirty = api.viewport.update(updateDelta) || isDirty; + + me.timer.lastUpdate = window.performance.now(); + updateAverageDelta = me.timer.lastUpdate - lastUpdateStart; + + accumulator -= accumulatorUpdateDelta; + if (me.sys.interpolation) { + accumulator = 0; + break; + } + } + } + }; + + /** + * draw all existing objects + * @name draw + * @memberOf me.game + * @private + * @ignore + * @function + * @param {me.Viewport} viewport viewport object + */ + api.draw = function (viewport) { + if (isDirty || isAlwaysDirty) { + viewport = viewport || api.viewport; + // cache the viewport rendering position, so that other object + // can access it later (e,g. entityContainer when drawing floating objects) + var translateX = viewport.pos.x + viewport.offset.x; + var translateY = viewport.pos.y + viewport.offset.y; + + // translate the world coordinates by default to screen coordinates + api.world.currentTransform.translate(-translateX, -translateY); + + // prepare renderer to draw a new frame + renderer.clear(); + + // save the current state + me.video.renderer.save(); + + // apply viewport transform if needed + if (!viewport.currentTransform.isIdentity()) { + renderer.transform(viewport.currentTransform); + } + + // update all objects, + // specifying the viewport as the rectangle area to redraw + api.world.draw(renderer, viewport); + + // restore + renderer.restore(); + + // translate the world coordinates by default to screen coordinates + api.world.currentTransform.translate(translateX, translateY); + + // draw the viewpor/camera effects + viewport.draw(renderer); + + } + + isDirty = false; + + // flush/render our frame + renderer.flush(); + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org/ + * + */ +(function () { + /** + * Convert first character of a string to uppercase, if it's a letter. + * @ignore + * @function + * @name capitalize + * @param {String} str Input string. + * @return {String} String with first letter made uppercase. + */ + var capitalize = function (str) { + return str.substring(0, 1).toUpperCase() + str.substring(1, str.length); + }; + + /** + * A collection of utilities to ease porting between different user agents. + * @namespace me.agent + * @memberOf me + */ + me.agent = (function () { + var api = {}; + + /** + * Known agent vendors + * @ignore + */ + var vendors = [ "ms", "MS", "moz", "webkit", "o" ]; + + /** + * Get a vendor-prefixed property + * @public + * @name prefixed + * @function + * @param {String} name Property name + * @param {Object} [obj=window] Object or element reference to access + * @return {Mixed} Value of property + * @memberOf me.agent + */ + api.prefixed = function (name, obj) { + obj = obj || window; + if (name in obj) { + return obj[name]; + } + + var uc_name = capitalize(name); + + var result; + vendors.some(function (vendor) { + var name = vendor + uc_name; + return (result = (name in obj) ? obj[name] : undefined); + }); + return result; + }; + + /** + * Set a vendor-prefixed property + * @public + * @name setPrefixed + * @function + * @param {String} name Property name + * @param {Mixed} value Property value + * @param {Object} [obj=window] Object or element reference to access + * @memberOf me.agent + */ + api.setPrefixed = function (name, value, obj) { + obj = obj || window; + if (name in obj) { + obj[name] = value; + return; + } + + var uc_name = capitalize(name); + + vendors.some(function (vendor) { + var name = vendor + uc_name; + if (name in obj) { + obj[name] = value; + return true; + } + return false; + }); + }; + + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A singleton object representing the device capabilities and specific events + * @namespace me.device + * @memberOf me + */ + me.device = (function () { + // defines object for holding public information/functionality. + var api = {}; + // private properties + var accelInitialized = false; + var deviceOrientationInitialized = false; + var devicePixelRatio = null; + + // swipe utility fn & flag + var swipeEnabled = true; + var disableSwipeFn = function (e) { + e.preventDefault(); + window.scroll(0, 0); + return false; + }; + + + /** + * check the device capapbilities + * @ignore + */ + api._check = function () { + + // detect device type/platform + me.device._detectDevice(); + + // Mobile browser hacks + if (me.device.isMobile && !me.device.cocoon) { + // Prevent the webview from moving on a swipe + api.enableSwipe(false); + } + + // future proofing (MS) feature detection + me.device.pointerEvent = me.agent.prefixed("PointerEvent", window); + me.device.maxTouchPoints = me.agent.prefixed("maxTouchPoints", navigator) || 0; + window.gesture = me.agent.prefixed("gesture"); + + // detect touch capabilities + me.device.touch = ("createTouch" in document) || ("ontouchstart" in window) || + (me.device.cocoon) || (me.device.pointerEvent && (me.device.maxTouchPoints > 0)); + + // accelerometer detection + me.device.hasAccelerometer = ( + (typeof (window.DeviceMotionEvent) !== "undefined") || ( + (typeof (window.Windows) !== "undefined") && + (typeof (Windows.Devices.Sensors.Accelerometer) === "function") + ) + ); + + // pointerlock detection + this.hasPointerLockSupport = me.agent.prefixed("pointerLockElement", document); + + if (this.hasPointerLockSupport) { + document.exitPointerLock = me.agent.prefixed("exitPointerLock", document); + } + + // device motion detection + if (window.DeviceOrientationEvent) { + me.device.hasDeviceOrientation = true; + } + + // fullscreen api detection & polyfill when possible + this.hasFullscreenSupport = me.agent.prefixed("fullscreenEnabled", document) || + document.mozFullScreenEnabled; + + document.exitFullscreen = me.agent.prefixed("cancelFullScreen", document) || + me.agent.prefixed("exitFullscreen", document); + + // vibration API poyfill + navigator.vibrate = me.agent.prefixed("vibrate", navigator); + + try { + api.localStorage = !!window.localStorage; + } catch (e) { + // the above generates an exception when cookies are blocked + api.localStorage = false; + } + + // set pause/stop action on losing focus + window.addEventListener("blur", function () { + if (me.sys.stopOnBlur) { + me.state.stop(true); + } + if (me.sys.pauseOnBlur) { + me.state.pause(true); + } + }, false); + // set restart/resume action on gaining focus + window.addEventListener("focus", function () { + if (me.sys.stopOnBlur) { + me.state.restart(true); + } + if (me.sys.resumeOnFocus) { + me.state.resume(true); + } + }, false); + + + // Set the name of the hidden property and the change event for visibility + var hidden, visibilityChange; + if (typeof document.hidden !== "undefined") { + // Opera 12.10 and Firefox 18 and later support + hidden = "hidden"; + visibilityChange = "visibilitychange"; + } else if (typeof document.mozHidden !== "undefined") { + hidden = "mozHidden"; + visibilityChange = "mozvisibilitychange"; + } else if (typeof document.msHidden !== "undefined") { + hidden = "msHidden"; + visibilityChange = "msvisibilitychange"; + } else if (typeof document.webkitHidden !== "undefined") { + hidden = "webkitHidden"; + visibilityChange = "webkitvisibilitychange"; + } + + // register on the event if supported + if (typeof (visibilityChange) === "string") { + // add the corresponding event listener + document.addEventListener(visibilityChange, + function () { + if (document[hidden]) { + if (me.sys.stopOnBlur) { + me.state.stop(true); + } + if (me.sys.pauseOnBlur) { + me.state.pause(true); + } + } else { + if (me.sys.stopOnBlur) { + me.state.restart(true); + } + if (me.sys.resumeOnFocus) { + me.state.resume(true); + } + } + }, false + ); + } + }; + + /** + * detect the device type + * @ignore + */ + api._detectDevice = function () { + // iOS Device ? + me.device.iOS = /iPhone|iPad|iPod/i.test(me.device.ua); + // Android Device ? + me.device.android = /Android/i.test(me.device.ua); + me.device.android2 = /Android 2/i.test(me.device.ua); + // Chrome OS ? + me.device.chromeOS = /CrOS/.test(me.device.ua); + // Windows Device ? + me.device.wp = /Windows Phone/i.test(me.device.ua); + // Kindle device ? + me.device.BlackBerry = /BlackBerry/i.test(me.device.ua); + // Kindle device ? + me.device.Kindle = /Kindle|Silk.*Mobile Safari/i.test(me.device.ua); + + // Mobile platform + me.device.isMobile = /Mobi/i.test(me.device.ua) || + me.device.iOS || + me.device.android || + me.device.wp || + me.device.BlackBerry || + me.device.Kindle || false; + // ejecta + me.device.ejecta = (typeof window.ejecta !== "undefined"); + + // cocoon/cocoonJS + me.device.cocoon = navigator.isCocoonJS || // former cocoonJS + (typeof window.Cocoon !== "undefined"); // new cocoon + + }; + + /* + * PUBLIC Properties & Functions + */ + + // Browser capabilities + + /** + * the `ua` read-only property returns the user agent string for the current browser. + * @type String + * @readonly + * @name ua + * @memberOf me.device + */ + api.ua = navigator.userAgent; + + /** + * Browser Local Storage capabilities
+ * (this flag will be set to false if cookies are blocked) + * @type Boolean + * @readonly + * @name localStorage + * @memberOf me.device + */ + api.localStorage = false; + + /** + * Browser accelerometer capabilities + * @type Boolean + * @readonly + * @name hasAccelerometer + * @memberOf me.device + */ + api.hasAccelerometer = false; + + /** + * Browser device orientation + * @type Boolean + * @readonly + * @name hasDeviceOrientation + * @memberOf me.device + */ + api.hasDeviceOrientation = false; + + /** + * Browser full screen support + * @type Boolean + * @readonly + * @name hasFullscreenSupport + * @memberOf me.device + */ + api.hasFullscreenSupport = false; + + /** + * Browser pointerlock api support + * @type Boolean + * @readonly + * @name hasPointerLockSupport + * @memberOf me.device + */ + api.hasPointerLockSupport = false; + + /** + * Browser Base64 decoding capability + * @type Boolean + * @readonly + * @name nativeBase64 + * @memberOf me.device + */ + api.nativeBase64 = (typeof(window.atob) === "function"); + + /** + * Return the maximum number of touch contacts of current device. + * @type Number + * @readonly + * @name maxTouchPoints + * @memberOf me.device + */ + api.maxTouchPoints = 0; + + /** + * Touch capabilities + * @type Boolean + * @readonly + * @name touch + * @memberOf me.device + */ + api.touch = false; + + /** + * equals to true if a mobile device
+ * (Android | iPhone | iPad | iPod | BlackBerry | Windows Phone | Kindle) + * @type Boolean + * @readonly + * @name isMobile + * @memberOf me.device + */ + api.isMobile = false; + + /** + * equals to true if the device is an iOS platform. + * @type Boolean + * @readonly + * @name iOS + * @memberOf me.device + */ + api.iOS = false; + + /** + * equals to true if the device is an Android platform. + * @type Boolean + * @readonly + * @name android + * @memberOf me.device + */ + api.android = false; + + /** + * equals to true if the device is an Android 2.x platform. + * @type Boolean + * @readonly + * @name android2 + * @memberOf me.device + */ + api.android2 = false; + + /** + * equals to true if the game is running under Ejecta. + * @type Boolean + * @readonly + * @see http://impactjs.com/ejecta + * @name ejecta + * @memberOf me.device + */ + api.ejecta = false; + + /** + * equals to true if the game is running under cocoon/cocoonJS. + * @type Boolean + * @readonly + * @see https://cocoon.io + * @name cocoon + * @memberOf me.device + */ + api.cocoon = false; + + /** + * equals to true if the device is running on ChromeOS. + * @type Boolean + * @readonly + * @name chromeOS + * @memberOf me.device + */ + api.chromeOS = false; + + + /** + * equals to true if the device is a Windows Phone platform. + * @type Boolean + * @readonly + * @name wp + * @memberOf me.device + */ + api.wp = false; + + /** + * equals to true if the device is a BlackBerry platform. + * @type Boolean + * @readonly + * @name BlackBerry + * @memberOf me.device + */ + api.BlackBerry = false; + + /** + * equals to true if the device is a Kindle platform. + * @type Boolean + * @readonly + * @name Kindle + * @memberOf me.device + */ + api.Kindle = false; + + /** + * The device current orientation status.
+ * 0 : default orientation
+ * 90 : 90 degrees clockwise from default
+ * -90 : 90 degrees anti-clockwise from default
+ * 180 : 180 degrees from default + * @type Number + * @readonly + * @name orientation + * @memberOf me.device + */ + api.orientation = 0; + + /** + * contains the g-force acceleration along the x-axis. + * @public + * @type Number + * @readonly + * @name accelerationX + * @memberOf me.device + */ + api.accelerationX = 0; + + /** + * contains the g-force acceleration along the y-axis. + * @public + * @type Number + * @readonly + * @name accelerationY + * @memberOf me.device + */ + api.accelerationY = 0; + + /** + * contains the g-force acceleration along the z-axis. + * @public + * @type Number + * @readonly + * @name accelerationZ + * @memberOf me.device + */ + api.accelerationZ = 0; + + /** + * Device orientation Gamma property. Gives angle on tilting a portrait held phone left or right + * @public + * @type Number + * @readonly + * @name gamma + * @memberOf me.device + */ + api.gamma = 0; + + /** + * Device orientation Beta property. Gives angle on tilting a portrait held phone forward or backward + * @public + * @type Number + * @readonly + * @name beta + * @memberOf me.device + */ + api.beta = 0; + + /** + * Device orientation Alpha property. Gives angle based on the rotation of the phone around its z axis. + * The z-axis is perpendicular to the phone, facing out from the center of the screen. + * @public + * @type Number + * @readonly + * @name alpha + * @memberOf me.device + */ + api.alpha = 0; + + /** + * a string representing the preferred language of the user, usually the language of the browser UI. + * (will default to "en" if the information is not available) + * @public + * @type String + * @readonly + * @see http://www.w3schools.com/tags/ref_language_codes.asp + * @name language + * @memberOf me.device + */ + api.language = navigator.language || navigator.browserLanguage || navigator.userLanguage || "en"; + + /** + * enable/disable swipe on WebView. + * @name enableSwipe + * @memberOf me.device + * @function + * @param {boolean} [enable=true] enable or disable swipe. + */ + api.enableSwipe = function (enable) { + if (enable !== false) { + if (swipeEnabled === false) { + window.document.removeEventListener("touchmove", disableSwipeFn, false); + swipeEnabled = true; + } + } else if (swipeEnabled === true) { + window.document.addEventListener("touchmove", disableSwipeFn, false); + swipeEnabled = false; + } + }; + + /** + * Triggers a fullscreen request. Requires fullscreen support from the browser/device. + * @name requestFullscreen + * @memberOf me.device + * @function + * @param {Object} [element=default canvas object] the element to be set in full-screen mode. + * @example + * // add a keyboard shortcut to toggle Fullscreen mode on/off + * me.input.bindKey(me.input.KEY.F, "toggleFullscreen"); + * me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) { + * // toggle fullscreen on/off + * if (action === "toggleFullscreen") { + * if (!me.device.isFullscreen) { + * me.device.requestFullscreen(); + * } else { + * me.device.exitFullscreen(); + * } + * } + * }); + */ + api.requestFullscreen = function (element) { + if (this.hasFullscreenSupport) { + element = element || me.video.getWrapper(); + element.requestFullscreen = me.agent.prefixed("requestFullscreen", element) || + element.mozRequestFullScreen; + + element.requestFullscreen(); + } + }; + + /** + * Exit fullscreen mode. Requires fullscreen support from the browser/device. + * @name exitFullscreen + * @memberOf me.device + * @function + */ + api.exitFullscreen = function () { + if (this.hasFullscreenSupport) { + document.exitFullscreen(); + } + }; + + /** + * return the device pixel ratio + * @name getPixelRatio + * @memberOf me.device + * @function + */ + api.getPixelRatio = function () { + + if (devicePixelRatio === null) { + var _context; + if (typeof me.video.renderer !== "undefined") { + _context = me.video.renderer.getScreenContext(); + } else { + _context = me.Renderer.prototype.getContext2d(document.createElement("canvas")); + } + var _devicePixelRatio = window.devicePixelRatio || 1, + _backingStoreRatio = me.agent.prefixed("backingStorePixelRatio", _context) || 1; + devicePixelRatio = _devicePixelRatio / _backingStoreRatio; + } + return devicePixelRatio; + }; + + /** + * return the device storage + * @name getStorage + * @memberOf me.device + * @function + * @param {String} [type="local"] + * @return me.save object + */ + api.getStorage = function (type) { + + type = type || "local"; + + switch (type) { + case "local" : + return me.save; + + default : + throw new me.Error("storage type " + type + " not supported"); + } + }; + + /** + * event management (Accelerometer) + * http://www.mobilexweb.com/samples/ball.html + * http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5 + * @ignore + */ + function onDeviceMotion(e) { + if (e.reading) { + // For Windows 8 devices + api.accelerationX = e.reading.accelerationX; + api.accelerationY = e.reading.accelerationY; + api.accelerationZ = e.reading.accelerationZ; + } + else { + // Accelerometer information + api.accelerationX = e.accelerationIncludingGravity.x; + api.accelerationY = e.accelerationIncludingGravity.y; + api.accelerationZ = e.accelerationIncludingGravity.z; + } + } + + function onDeviceRotate(e) { + api.gamma = e.gamma; + api.beta = e.beta; + api.alpha = e.alpha; + } + + /** + * Enters pointer lock, requesting it from the user first. Works on supported devices & browsers + * Must be called in a click event or an event that requires user interaction. + * If you need to run handle events for errors or change of the pointer lock, see below. + * @name turnOnPointerLock + * @memberOf me.device + * @function + * @example + * document.addEventListener("pointerlockchange", pointerlockchange, false); + * document.addEventListener("mozpointerlockchange", pointerlockchange, false); + * document.addEventListener("webkitpointerlockchange", pointerlockchange, false); + * + * document.addEventListener("pointerlockerror", pointerlockerror, false); + * document.addEventListener("mozpointerlockerror", pointerlockerror, false); + * document.addEventListener("webkitpointerlockerror", pointerlockerror, false); + */ + api.turnOnPointerLock = function () { + if (this.hasPointerLockSupport) { + var element = me.video.getWrapper(); + if (me.device.ua.match(/Firefox/i)) { + var fullscreenchange = function () { + if ((me.agent.prefixed("fullscreenElement", document) || + document.mozFullScreenElement) === element) { + + document.removeEventListener("fullscreenchange", fullscreenchange); + document.removeEventListener("mozfullscreenchange", fullscreenchange); + element.requestPointerLock = me.agent.prefixed("requestPointerLock", element); + element.requestPointerLock(); + } + }; + + document.addEventListener("fullscreenchange", fullscreenchange, false); + document.addEventListener("mozfullscreenchange", fullscreenchange, false); + + me.device.requestFullscreen(); + + } + else { + element.requestPointerLock(); + } + } + }; + + /** + * Exits pointer lock. Works on supported devices & browsers + * @name turnOffPointerLock + * @memberOf me.device + * @function + */ + api.turnOffPointerLock = function () { + if (this.hasPointerLockSupport) { + document.exitPointerLock(); + } + }; + + /** + * watch Accelerator event + * @name watchAccelerometer + * @memberOf me.device + * @public + * @function + * @return {Boolean} false if not supported by the device + */ + api.watchAccelerometer = function () { + if (me.device.hasAccelerometer) { + if (!accelInitialized) { + if (typeof Windows === "undefined") { + // add a listener for the devicemotion event + window.addEventListener("devicemotion", onDeviceMotion, false); + } + else { + // On Windows 8 Device + var accelerometer = Windows.Devices.Sensors.Accelerometer.getDefault(); + if (accelerometer) { + // Capture event at regular intervals + var minInterval = accelerometer.minimumReportInterval; + var Interval = minInterval >= 16 ? minInterval : 25; + accelerometer.reportInterval = Interval; + + accelerometer.addEventListener("readingchanged", onDeviceMotion, false); + } + } + accelInitialized = true; + } + return true; + } + return false; + }; + + /** + * unwatch Accelerometor event + * @name unwatchAccelerometer + * @memberOf me.device + * @public + * @function + */ + api.unwatchAccelerometer = function () { + if (accelInitialized) { + if (typeof Windows === "undefined") { + // add a listener for the mouse + window.removeEventListener("devicemotion", onDeviceMotion, false); + } else { + // On Windows 8 Devices + var accelerometer = Windows.Device.Sensors.Accelerometer.getDefault(); + + accelerometer.removeEventListener("readingchanged", onDeviceMotion, false); + } + accelInitialized = false; + } + }; + + /** + * watch the device orientation event + * @name watchDeviceOrientation + * @memberOf me.device + * @public + * @function + * @return {Boolean} false if not supported by the device + */ + api.watchDeviceOrientation = function () { + if (me.device.hasDeviceOrientation && !deviceOrientationInitialized) { + window.addEventListener("deviceorientation", onDeviceRotate, false); + deviceOrientationInitialized = true; + } + return false; + }; + + /** + * unwatch Device orientation event + * @name unwatchDeviceOrientation + * @memberOf me.device + * @public + * @function + */ + api.unwatchDeviceOrientation = function () { + if (deviceOrientationInitialized) { + window.removeEventListener("deviceorientation", onDeviceRotate, false); + deviceOrientationInitialized = false; + } + }; + + /** + * the vibrate method pulses the vibration hardware on the device,
+ * If the device doesn't support vibration, this method has no effect.
+ * If a vibration pattern is already in progress when this method is called, + * the previous pattern is halted and the new one begins instead. + * @name vibrate + * @memberOf me.device + * @public + * @function + * @param {Number|Number[]} pattern pattern of vibration and pause intervals + * @example + * // vibrate for 1000 ms + * navigator.vibrate(1000); + * // or alternatively + * navigator.vibrate([1000]); + * // vibrate for 50 ms, be still for 100 ms, and then vibrate for 150 ms: + * navigator.vibrate([50, 100, 150]); + * // cancel any existing vibrations + * navigator.vibrate(0); + */ + api.vibrate = function (pattern) { + if (navigator.vibrate) { + navigator.vibrate(pattern); + } + }; + + + return api; + })(); + + /** + * Returns true if the browser/device is in full screen mode. + * @name isFullscreen + * @memberOf me.device + * @public + * @type Boolean + * @readonly + * @return {boolean} + */ + Object.defineProperty(me.device, "isFullscreen", { + /** + * @ignore + */ + get: function () { + if (me.device.hasFullscreenSupport) { + var el = me.agent.prefixed("fullscreenElement", document) || + document.mozFullScreenElement; + return (el === me.video.getWrapper()); + } else { + return false; + } + } + }); + + /** + * Returns true if the browser/device has audio capabilities. + * @name sound + * @memberOf me.device + * @public + * @type Boolean + * @readonly + * @return {boolean} + */ + Object.defineProperty(me.device, "sound", { + /** + * @ignore + */ + get: function () { + return !Howler.noAudio; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a Timer object to manage time function (FPS, Game Tick, Time...)

+ * There is no constructor function for me.timer + * @namespace me.timer + * @memberOf me + */ + me.timer = (function () { + // hold public stuff in our api + var api = {}; + + /* + * PRIVATE STUFF + */ + + //hold element to display fps + var framecount = 0; + var framedelta = 0; + + /* fps count stuff */ + var last = 0; + var now = 0; + var delta = 0; + var step = Math.ceil(1000 / me.sys.fps); // ROUND IT ? + // define some step with some margin + var minstep = (1000 / me.sys.fps) * 1.25; // IS IT NECESSARY?\ + + // list of defined timer function + var timers = []; + var timerId = 0; + + /** + * @ignore + */ + var clearTimer = function (timerId) { + for (var i = 0, len = timers.length; i < len; i++) { + if (timers[i].timerId === timerId) { + timers.splice(i, 1); + break; + } + } + }; + + /** + * update timers + * @ignore + */ + var updateTimers = function (dt) { + for (var i = 0, len = timers.length; i < len; i++) { + var _timer = timers[i]; + if (!(_timer.pauseable && me.state.isPaused())) { + _timer.elapsed += dt; + } + if (_timer.elapsed >= _timer.delay) { + _timer.fn.apply(this); + if (_timer.repeat === true) { + _timer.elapsed -= _timer.delay; + } else { + me.timer.clearTimeout(_timer.timerId); + } + } + } + }; + + /* + * PUBLIC STUFF + */ + + /** + * Last game tick value.
+ * Use this value to scale velocities during frame drops due to slow + * hardware or when setting an FPS limit. (See {@link me.sys.fps}) + * This feature is disabled by default. Enable me.sys.interpolation to + * use it. + * @public + * @see me.sys.interpolation + * @type Number + * @name tick + * @memberOf me.timer + */ + api.tick = 1.0; + + /** + * Last measured fps rate.
+ * This feature is disabled by default. Load and enable the DebugPanel + * plugin to use it. + * @public + * @type Number + * @name fps + * @memberOf me.timer + */ + api.fps = 0; + + /** + * Last update time.
+ * Use this value to implement frame prediction in drawing events, + * for creating smooth motion while running game update logic at + * a lower fps. + * @public + * @type Date + * @name lastUpdate + * @memberOf me.timer + */ + api.lastUpdate = window.performance.now(); + + /** + * init the timer + * @ignore + */ + api.init = function () { + // reset variables to initial state + api.reset(); + now = last = 0; + }; + + /** + * reset time (e.g. usefull in case of pause) + * @name reset + * @memberOf me.timer + * @ignore + * @function + */ + api.reset = function () { + // set to "now" + last = now = window.performance.now(); + delta = 0; + // reset delta counting variables + framedelta = 0; + framecount = 0; + }; + + /** + * Calls a function once after a specified delay. + * @name setTimeout + * @memberOf me.timer + * @param {Function} fn the function you want to execute after delay milliseconds. + * @param {Number} delay the number of milliseconds (thousandths of a second) that the function call should be delayed by. + * @param {Boolean} [pauseable=true] respects the pause state of the engine. + * @return {Number} The numerical ID of the timeout, which can be used later with me.timer.clearTimeout(). + * @function + */ + api.setTimeout = function (fn, delay, pauseable) { + timers.push({ + fn : fn, + delay : delay, + elapsed : 0, + repeat : false, + timerId : ++timerId, + pauseable : pauseable === true || true + }); + return timerId; + }; + + /** + * Calls a function at specified interval. + * @name setInterval + * @memberOf me.timer + * @param {Function} fn the function to execute + * @param {Number} delay the number of milliseconds (thousandths of a second) on how often to execute the function + * @param {Boolean} [pauseable=true] respects the pause state of the engine. + * @return {Number} The numerical ID of the timeout, which can be used later with me.timer.clearInterval(). + * @function + */ + api.setInterval = function (fn, delay, pauseable) { + timers.push({ + fn : fn, + delay : delay, + elapsed : 0, + repeat : true, + timerId : ++timerId, + pauseable : pauseable === true || true + }); + return timerId; + }; + + /** + * Clears the delay set by me.timer.setTimeout(). + * @name clearTimeout + * @memberOf me.timer + * @function + * @param {Number} timeoutID ID of the timeout to be cleared + */ + api.clearTimeout = function (timeoutID) { + clearTimer.defer(this, timeoutID); + }; + + /** + * Clears the Interval set by me.timer.setInterval(). + * @name clearInterval + * @memberOf me.timer + * @function + * @param {Number} intervalID ID of the interval to be cleared + */ + api.clearInterval = function (intervalID) { + clearTimer.defer(this, intervalID); + }; + + /** + * Return the current timestamp in milliseconds
+ * since the game has started or since linux epoch (based on browser support for High Resolution Timer) + * @name getTime + * @memberOf me.timer + * @return {Number} + * @function + */ + api.getTime = function () { + return now; + }; + + /** + * Return elapsed time in milliseconds since the last update
+ * @name getDelta + * @memberOf me.timer + * @return {Number} + * @function + */ + api.getDelta = function () { + return delta; + }; + + /** + * compute the actual frame time and fps rate + * @name computeFPS + * @ignore + * @memberOf me.timer + * @function + */ + api.countFPS = function () { + framecount++; + framedelta += delta; + if (framecount % 10 === 0) { + this.fps = (~~((1000 * framecount) / framedelta)).clamp(0, me.sys.fps); + framedelta = 0; + framecount = 0; + } + }; + + /** + * update game tick + * should be called once a frame + * @param {Number} time current timestamp as provided by the RAF callback + * @return {Number} time elapsed since the last update + * @ignore + */ + api.update = function (time) { + last = now; + now = time; + delta = (now - last); + + // get the game tick + api.tick = (delta > minstep && me.sys.interpolation) ? delta / step : 1; + + // update defined timers + updateTimers(delta); + + return delta; + }; + + // return our apiect + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A pool of Object entity
+ * This object is used for object pooling - a technique that might speed up your game + * if used properly.
+ * If some of your classes will be instantiated and removed a lot at a time, it is a + * good idea to add the class to this entity pool. A separate pool for that class + * will be created, which will reuse objects of the class. That way they won't be instantiated + * each time you need a new one (slowing your game), but stored into that pool and taking one + * already instantiated when you need it.

+ * This object is also used by the engine to instantiate objects defined in the map, + * which means, that on level loading the engine will try to instantiate every object + * found in the map, based on the user defined name in each Object Properties
+ *
+ * @namespace me.pool + * @memberOf me + */ + me.pool = (function () { + // hold public stuff in our singleton + var api = {}; + + var entityClass = {}; + + /* + * PUBLIC STUFF + */ + + /** + * Constructor + * @ignore + */ + api.init = function () { + // add default entity object + api.register("me.Entity", me.Entity); + api.register("me.CollectableEntity", me.CollectableEntity); + api.register("me.LevelEntity", me.LevelEntity); + api.register("me.Tween", me.Tween, true); + api.register("me.Color", me.Color, true); + api.register("me.Particle", me.Particle, true); + api.register("me.Sprite", me.Sprite); + api.register("me.Vector2d", me.Vector2d, true); + api.register("me.Glyph", me.Glyph, true); + api.register("me.Matrix2d", me.Matrix2d, true); + }; + + /** + * register an object to the pool.
+ * Pooling must be set to true if more than one such objects will be created.
+ * (note) If pooling is enabled, you shouldn't instantiate objects with `new`. + * See examples in {@link me.pool#pull} + * @name register + * @memberOf me.pool + * @public + * @function + * @param {String} className as defined in the Name field of the Object Properties (in Tiled) + * @param {Object} class corresponding Class to be instantiated + * @param {Boolean} [objectPooling=false] enables object pooling for the specified class + * - speeds up the game by reusing existing objects + * @example + * // add our users defined entities in the entity pool + * me.pool.register("playerspawnpoint", PlayerEntity); + * me.pool.register("cherryentity", CherryEntity, true); + * me.pool.register("heartentity", HeartEntity, true); + * me.pool.register("starentity", StarEntity, true); + */ + api.register = function (className, classObj, pooling) { + if (typeof (classObj) !== "undefined") { + entityClass[className] = { + "class" : classObj, + "pool" : (pooling ? [] : undefined) + }; + } else { + throw new me.Error("Cannot register object '" + className + "', invalid class"); + } + }; + + /** + * Pull a new instance of the requested object (if added into the object pool) + * @name pull + * @memberOf me.pool + * @public + * @function + * @param {String} className as used in {@link me.pool.register} + * @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object + * @return {Object} the instance of the requested object + * @example + * me.pool.register("player", PlayerEntity); + * var player = me.pool.pull("player"); + * @example + * me.pool.register("bullet", BulletEntity, true); + * me.pool.register("enemy", EnemyEntity, true); + * // ... + * // when we need to manually create a new bullet: + * var bullet = me.pool.pull("bullet", x, y, direction); + * // ... + * // params aren't a fixed number + * // when we need new enemy we can add more params, that the object construct requires: + * var enemy = me.pool.pull("enemy", x, y, direction, speed, power, life); + * // ... + * // when we want to destroy existing object, the remove + * // function will ensure the object can then be reallocated later + * me.game.world.removeChild(enemy); + * me.game.world.removeChild(bullet); + */ + api.pull = function (name) { + var args = new Array(arguments.length); + for (var i = 0; i < arguments.length; i++) { + args[i] = arguments[i]; + } + var entity = entityClass[name]; + if (entity) { + var proto = entity["class"], + pool = entity.pool, + obj; + + if (pool && ((obj = pool.pop()))) { + args.shift(); + // call the object onResetEvent function if defined + if (typeof(obj.onResetEvent) === "function") { + obj.onResetEvent.apply(obj, args); + } + else { + obj.init.apply(obj, args); + } + } + else { + args[0] = proto; + obj = new (proto.bind.apply(proto, args))(); + if (pool) { + obj.className = name; + } + } + return obj; + } + + throw new me.Error("Cannot instantiate entity of type '" + name + "'"); + }; + + /** + * purge the entity pool from any inactive object
+ * Object pooling must be enabled for this function to work
+ * note: this will trigger the garbage collector + * @name purge + * @memberOf me.pool + * @public + * @function + */ + api.purge = function () { + for (var className in entityClass) { + if (entityClass[className]) { + entityClass[className].pool = []; + } + } + }; + + /** + * Push back an object instance into the entity pool
+ * Object pooling for the object class must be enabled, + * and object must have been instantiated using {@link me.pool#pull}, + * otherwise this function won't work + * @name push + * @memberOf me.pool + * @public + * @function + * @param {Object} instance to be recycled + */ + api.push = function (obj) { + var name = obj.className; + if (typeof(name) === "undefined" || !entityClass[name]) { + // object is not registered, don't do anything + return; + } + // store back the object instance for later recycling + entityClass[name].pool.push(obj); + }; + + /** + * Check if an object with the provided name is registered + * @name exists + * @memberOf me.pool + * @public + * @function + * @param {String} name of the registered object + * @return {Boolean} true if the classname is registered + */ + api.exists = function (name) { + return name in entityClass; + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a generic 2D Vector Object + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Number} [x=0] x value of the vector + * @param {Number} [y=0] y value of the vector + */ + me.Vector2d = me.Object.extend( + /** @scope me.Vector2d.prototype */ + { + /** @ignore */ + init : function (x, y) { + return this.set(x || 0, y || 0); + }, + + /** + * @ignore */ + _set : function (x, y) { + this.x = x; + this.y = y; + return this; + }, + + /** + * set the Vector x and y properties to the given values
+ * @name set + * @memberOf me.Vector2d + * @function + * @param {Number} x + * @param {Number} y + * @return {me.Vector2d} Reference to this object for method chaining + */ + set : function (x, y) { + if (x !== +x || y !== +y) { + throw new me.Vector2d.Error( + "invalid x,y parameters (not a number)" + ); + } + + /** + * x value of the vector + * @public + * @type Number + * @name x + * @memberOf me.Vector2d + */ + //this.x = x; + + /** + * y value of the vector + * @public + * @type Number + * @name y + * @memberOf me.Vector2d + */ + //this.y = y; + + return this._set(x, y); + }, + + /** + * set the Vector x and y properties to 0 + * @name setZero + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + setZero : function () { + return this.set(0, 0); + }, + + /** + * set the Vector x and y properties using the passed vector + * @name setV + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + setV : function (v) { + return this._set(v.x, v.y); + }, + + /** + * Add the passed vector to this vector + * @name add + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + add : function (v) { + return this._set(this.x + v.x, this.y + v.y); + }, + + /** + * Substract the passed vector to this vector + * @name sub + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + sub : function (v) { + return this._set(this.x - v.x, this.y - v.y); + }, + + /** + * Multiply this vector values by the given scalar + * @name scale + * @memberOf me.Vector2d + * @function + * @param {Number} x + * @param {Number} [y=x] + * @return {me.Vector2d} Reference to this object for method chaining + */ + scale : function (x, y) { + return this._set(this.x * x, this.y * (typeof (y) !== "undefined" ? y : x)); + }, + + /** + * Convert this vector into isometric coordinate space + * @name toIso + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + toIso : function () { + return this._set(this.x - this.y, (this.x + this.y) * 0.5); + }, + + /** + * Convert this vector into 2d coordinate space + * @name to2d + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + to2d : function () { + return this._set(this.y + this.x / 2, this.y - this.x / 2); + }, + + /** + * Multiply this vector values by the passed vector + * @name scaleV + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + scaleV : function (v) { + return this._set(this.x * v.x, this.y * v.y); + }, + + /** + * Divide this vector values by the passed value + * @name div + * @memberOf me.Vector2d + * @function + * @param {Number} value + * @return {me.Vector2d} Reference to this object for method chaining + */ + div : function (n) { + return this._set(this.x / n, this.y / n); + }, + + /** + * Update this vector values to absolute values + * @name abs + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + abs : function () { + return this._set((this.x < 0) ? -this.x : this.x, (this.y < 0) ? -this.y : this.y); + }, + + /** + * Clamp the vector value within the specified value range + * @name clamp + * @memberOf me.Vector2d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.Vector2d} new me.Vector2d + */ + clamp : function (low, high) { + return new me.Vector2d(this.x.clamp(low, high), this.y.clamp(low, high)); + }, + + /** + * Clamp this vector value within the specified value range + * @name clampSelf + * @memberOf me.Vector2d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.Vector2d} Reference to this object for method chaining + */ + clampSelf : function (low, high) { + return this._set(this.x.clamp(low, high), this.y.clamp(low, high)); + }, + + /** + * Update this vector with the minimum value between this and the passed vector + * @name minV + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + minV : function (v) { + return this._set((this.x < v.x) ? this.x : v.x, (this.y < v.y) ? this.y : v.y); + }, + + /** + * Update this vector with the maximum value between this and the passed vector + * @name maxV + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + maxV : function (v) { + return this._set((this.x > v.x) ? this.x : v.x, (this.y > v.y) ? this.y : v.y); + }, + + /** + * Floor the vector values + * @name floor + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} new me.Vector2d + */ + floor : function () { + return new me.Vector2d(Math.floor(this.x), Math.floor(this.y)); + }, + + /** + * Floor this vector values + * @name floorSelf + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + floorSelf : function () { + return this._set(Math.floor(this.x), Math.floor(this.y)); + }, + + /** + * Ceil the vector values + * @name ceil + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} new me.Vector2d + */ + ceil : function () { + return new me.Vector2d(Math.ceil(this.x), Math.ceil(this.y)); + }, + + /** + * Ceil this vector values + * @name ceilSelf + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + ceilSelf : function () { + return this._set(Math.ceil(this.x), Math.ceil(this.y)); + }, + + /** + * Negate the vector values + * @name negate + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} new me.Vector2d + */ + negate : function () { + return new me.Vector2d(-this.x, -this.y); + }, + + /** + * Negate this vector values + * @name negateSelf + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + negateSelf : function () { + return this._set(-this.x, -this.y); + }, + + /** + * Copy the x,y values of the passed vector to this one + * @name copy + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {me.Vector2d} Reference to this object for method chaining + */ + copy : function (v) { + return this._set(v.x, v.y); + }, + + /** + * return true if the two vectors are the same + * @name equals + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {Boolean} + */ + equals : function (v) { + return ((this.x === v.x) && (this.y === v.y)); + }, + + /** + * normalize this vector (scale the vector so that its magnitude is 1) + * @name normalize + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + normalize : function () { + var d = this.length(); + if (d > 0) { + return this._set(this.x / d, this.y / d); + } + return this; + }, + + /** + * change this vector to be perpendicular to what it was before.
+ * (Effectively rotates it 90 degrees in a clockwise direction) + * @name perp + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} Reference to this object for method chaining + */ + perp : function () { + return this._set(this.y, -this.x); + }, + + /** + * Rotate this vector (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.Vector2d + * @function + * @param {number} angle The angle to rotate (in radians) + * @return {me.Vector2d} Reference to this object for method chaining + */ + rotate : function (angle) { + var x = this.x; + var y = this.y; + return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle)); + }, + + /** + * return the dot product of this vector and the passed one + * @name dotProduct + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {Number} The dot product. + */ + dotProduct : function (v) { + return this.x * v.x + this.y * v.y; + }, + + /** + * return the square length of this vector + * @name length2 + * @memberOf me.Vector2d + * @function + * @return {Number} The length^2 of this vector. + */ + length2 : function () { + return this.dotProduct(this); + }, + + /** + * return the length (magnitude) of this vector + * @name length + * @memberOf me.Vector2d + * @function + * @return {Number} the length of this vector + */ + length : function () { + return Math.sqrt(this.length2()); + }, + + /** + * return the distance between this vector and the passed one + * @name distance + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {Number} + */ + distance : function (v) { + var dx = this.x - v.x, dy = this.y - v.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * return the angle between this vector and the passed one + * @name angle + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v + * @return {Number} angle in radians + */ + angle : function (v) { + return Math.acos((this.dotProduct(v) / (this.length() * v.length())).clamp(-1, 1)); + }, + + /** + * project this vector on to another vector. + * @name project + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v The vector to project onto. + * @return {me.Vector2d} Reference to this object for method chaining + */ + project : function (v) { + return this.scale(this.dotProduct(v) / v.length2()); + }, + + /** + * Project this vector onto a vector of unit length.
+ * This is slightly more efficient than `project` when dealing with unit vectors. + * @name projectN + * @memberOf me.Vector2d + * @function + * @param {me.Vector2d} v The unit vector to project onto. + * @return {me.Vector2d} Reference to this object for method chaining + */ + projectN : function (v) { + return this.scale(this.dotProduct(v)); + }, + + /** + * return a clone copy of this vector + * @name clone + * @memberOf me.Vector2d + * @function + * @return {me.Vector2d} new me.Vector2d + */ + clone : function () { + return new me.Vector2d(this.x, this.y); + }, + + /** + * convert the object to a string representation + * @name toString + * @memberOf me.Vector2d + * @function + * @return {String} + */ + toString : function () { + return "x:" + this.x + ",y:" + this.y; + } + }); + + /** + * Base class for Vector2d exception handling. + * @name Error + * @class + * @memberOf me.Vector2d + * @constructor + * @param {String} msg Error message. + */ + me.Vector2d.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Vector2d.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a generic 3D Vector Object + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Number} [x=0] x value of the vector + * @param {Number} [y=0] y value of the vector + * @param {Number} [z=0] z value of the vector + */ + me.Vector3d = me.Object.extend( + /** @scope me.Vector3d.prototype */ + { + /** @ignore */ + init : function (x, y, z) { + return this.set(x || 0, y || 0, z || 0); + }, + + /** + * @ignore */ + _set : function (x, y, z) { + this.x = x; + this.y = y; + this.z = z; + return this; + }, + + /** + * set the Vector x and y properties to the given values
+ * @name set + * @memberOf me.Vector3d + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} z + * @return {me.Vector3d} Reference to this object for method chaining + */ + set : function (x, y, z) { + if (x !== +x || y !== +y || z !== +z) { + throw new me.Vector3d.Error( + "invalid x, y, z parameters (not a number)" + ); + } + + /** + * x value of the vector + * @public + * @type Number + * @name x + * @memberOf me.Vector3d + */ + //this.x = x; + + /** + * y value of the vector + * @public + * @type Number + * @name y + * @memberOf me.Vector3d + */ + //this.y = y; + + /** + * z value of the vector + * @public + * @type Number + * @name z + * @memberOf me.Vector3d + */ + //this.z = z; + + return this._set(x, y, z); + }, + + /** + * set the Vector x and y properties to 0 + * @name setZero + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + setZero : function () { + return this.set(0, 0, 0); + }, + + /** + * set the Vector x and y properties using the passed vector + * @name setV + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + setV : function (v) { + return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this.z); + }, + + /** + * Add the passed vector to this vector + * @name add + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + add : function (v) { + return this._set(this.x + v.x, this.y + v.y, this.z + (v.z || 0)); + }, + + /** + * Substract the passed vector to this vector + * @name sub + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + sub : function (v) { + return this._set(this.x - v.x, this.y - v.y, this.z - (v.z || 0)); + }, + + /** + * Multiply this vector values by the given scalar + * @name scale + * @memberOf me.Vector3d + * @function + * @param {Number} x + * @param {Number} [y=x] + * @param {Number} [z=x] + * @return {me.Vector3d} Reference to this object for method chaining + */ + scale : function (x, y, z) { + y = (typeof (y) !== "undefined" ? y : x); + z = (typeof (z) !== "undefined" ? z : x); + return this._set(this.x * x, this.y * y, this.z * z); + }, + + /** + * Multiply this vector values by the passed vector + * @name scaleV + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + scaleV : function (v) { + return this._set(this.x * v.x, this.y * v.y, this.z * (v.z || 1)); + }, + + /** + * Convert this vector into isometric coordinate space + * @name toIso + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + toIso : function () { + return this._set(this.x - this.y, (this.x + this.y) * 0.5, this.z); + }, + + /** + * Convert this vector into 2d coordinate space + * @name to2d + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + to2d : function () { + return this._set(this.y + this.x / 2, this.y - this.x / 2, this.z); + }, + + /** + * Divide this vector values by the passed value + * @name div + * @memberOf me.Vector3d + * @function + * @param {Number} value + * @return {me.Vector3d} Reference to this object for method chaining + */ + div : function (n) { + return this._set(this.x / n, this.y / n, this.z / n); + }, + + /** + * Update this vector values to absolute values + * @name abs + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + abs : function () { + return this._set((this.x < 0) ? -this.x : this.x, (this.y < 0) ? -this.y : this.y, (this.z < 0) ? -this.Z : this.z); + }, + + /** + * Clamp the vector value within the specified value range + * @name clamp + * @memberOf me.Vector3d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.Vector3d} new me.Vector3d + */ + clamp : function (low, high) { + return new me.Vector3d(this.x.clamp(low, high), this.y.clamp(low, high), this.z.clamp(low, high)); + }, + + /** + * Clamp this vector value within the specified value range + * @name clampSelf + * @memberOf me.Vector3d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.Vector3d} Reference to this object for method chaining + */ + clampSelf : function (low, high) { + return this._set(this.x.clamp(low, high), this.y.clamp(low, high), this.z.clamp(low, high)); + }, + + /** + * Update this vector with the minimum value between this and the passed vector + * @name minV + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + minV : function (v) { + var _vz = v.z || 0; + return this._set((this.x < v.x) ? this.x : v.x, (this.y < v.y) ? this.y : v.y, (this.z < _vz) ? this.z : _vz); + }, + + /** + * Update this vector with the maximum value between this and the passed vector + * @name maxV + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + maxV : function (v) { + var _vz = v.z || 0; + return this._set((this.x > v.x) ? this.x : v.x, (this.y > v.y) ? this.y : v.y, (this.z > _vz) ? this.z : _vz); + }, + + /** + * Floor the vector values + * @name floor + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} new me.Vector3d + */ + floor : function () { + return new me.Vector3d(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z)); + }, + + /** + * Floor this vector values + * @name floorSelf + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + floorSelf : function () { + return this._set(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z)); + }, + + /** + * Ceil the vector values + * @name ceil + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} new me.Vector3d + */ + ceil : function () { + return new me.Vector3d(Math.ceil(this.x), Math.ceil(this.y), Math.ceil(this.z)); + }, + + /** + * Ceil this vector values + * @name ceilSelf + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + ceilSelf : function () { + return this._set(Math.ceil(this.x), Math.ceil(this.y), Math.ceil(this.z)); + }, + + /** + * Negate the vector values + * @name negate + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} new me.Vector3d + */ + negate : function () { + return new me.Vector3d(-this.x, -this.y, -this.z); + }, + + /** + * Negate this vector values + * @name negateSelf + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + negateSelf : function () { + return this._set(-this.x, -this.y, -this.z); + }, + + /** + * Copy the x,y values of the passed vector to this one + * @name copy + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {me.Vector3d} Reference to this object for method chaining + */ + copy : function (v) { + return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this.z); + }, + + /** + * return true if the two vectors are the same + * @name equals + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {Boolean} + */ + equals : function (v) { + return ((this.x === v.x) && (this.y === v.y) && (this.z === (v.z || this.z))); + }, + + /** + * normalize this vector (scale the vector so that its magnitude is 1) + * @name normalize + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + normalize : function () { + var d = this.length(); + if (d > 0) { + return this._set(this.x / d, this.y / d, this.z / d); + } + return this; + }, + + /** + * change this vector to be perpendicular to what it was before.
+ * (Effectively rotates it 90 degrees in a clockwise direction around the z axis) + * @name perp + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} Reference to this object for method chaining + */ + perp : function () { + return this._set(this.y, -this.x, this.z); + }, + + /** + * Rotate this vector (counter-clockwise) by the specified angle (in radians) around the z axis + * @name rotate + * @memberOf me.Vector3d + * @function + * @param {number} angle The angle to rotate (in radians) + * @return {me.Vector3d} Reference to this object for method chaining + */ + rotate : function (angle) { + var x = this.x; + var y = this.y; + return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle), this.z); + }, + + /** + * return the dot product of this vector and the passed one + * @name dotProduct + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {Number} The dot product. + */ + dotProduct : function (v) { + return this.x * v.x + this.y * v.y + this.z * (v.z || 1); + }, + + /** + * return the square length of this vector + * @name length2 + * @memberOf me.Vector3d + * @function + * @return {Number} The length^2 of this vector. + */ + length2 : function () { + return this.dotProduct(this); + }, + + /** + * return the length (magnitude) of this vector + * @name length + * @memberOf me.Vector3d + * @function + * @return {Number} the length of this vector + */ + length : function () { + return Math.sqrt(this.length2()); + }, + + /** + * return the distance between this vector and the passed one + * @name distance + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {Number} + */ + distance : function (v) { + var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - (v.z || 0); + return Math.sqrt(dx * dx + dy * dy + dz * dz); + }, + + /** + * return the angle between this vector and the passed one + * @name angle + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v + * @return {Number} angle in radians + */ + angle : function (v) { + return Math.acos((this.dotProduct(v) / (this.length() * v.length())).clamp(-1, 1)); + }, + + /** + * project this vector on to another vector. + * @name project + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v The vector to project onto. + * @return {me.Vector3d} Reference to this object for method chaining + */ + project : function (v) { + return this.scale(this.dotProduct(v) / v.length2()); + }, + + /** + * Project this vector onto a vector of unit length.
+ * This is slightly more efficient than `project` when dealing with unit vectors. + * @name projectN + * @memberOf me.Vector3d + * @function + * @param {me.Vector2d|me.Vector3d} v The unit vector to project onto. + * @return {me.Vector3d} Reference to this object for method chaining + */ + projectN : function (v) { + return this.scale(this.dotProduct(v)); + }, + + /** + * return a clone copy of this vector + * @name clone + * @memberOf me.Vector3d + * @function + * @return {me.Vector3d} new me.Vector3d + */ + clone : function () { + return new me.Vector3d(this.x, this.y, this.z); + }, + + /** + * convert the object to a string representation + * @name toString + * @memberOf me.Vector3d + * @function + * @return {String} + */ + toString : function () { + return "x:" + this.x + ",y:" + this.y + ",z:" + this.z; + } + }); + + /** + * Base class for Vector3d exception handling. + * @name Error + * @class + * @memberOf me.Vector3d + * @constructor + * @param {String} msg Error message. + */ + me.Vector3d.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Vector3d.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A Vector2d object that provide notification by executing the given callback when the vector is changed. + * @class + * @extends me.Vector2d + * @constructor + * @param {Number} [x=0] x value of the vector + * @param {Number} [y=0] y value of the vector + * @param {Object} settings additional required parameters + * @param {Function} settings.onUpdate the callback to be executed when the vector is changed + */ + me.ObservableVector2d = me.Vector2d.extend({ + /** @scope me.ObservableVector2d.prototype */ + + /** @ignore */ + init : function (x, y, settings) { + /** + * x value of the vector + * @public + * @type Number + * @name x + * @memberOf me.ObservableVector2d + */ + Object.defineProperty(this, "x", { + /** + * @ignore + */ + get : function () { + return this._x; + }, + /** + * @ignore + */ + set : function (value) { + this.onUpdate(value, this._y, this._x, this._y); + this._x = value; + } + }); + + /** + * y value of the vector + * @public + * @type Number + * @name y + * @memberOf me.ObservableVector2d + */ + Object.defineProperty(this, "y", { + /** + * @ignore + */ + get : function () { + return this._y; + }, + /** + * @ignore + */ + set : function (value) { + this.onUpdate(this._x, value, this._x, this._y); + this._y = value; + } + }); + + if (typeof(settings) === "undefined") { + throw new me.ObservableVector2d.Error( + "undefined `onUpdate` callback" + ); + } + this.setCallback(settings.onUpdate); + this._x = x || 0; + this._y = y || 0; + }, + + /** @ignore */ + _set : function (x, y) { + this.onUpdate(x, y, this._x, this._y); + this._x = x; + this._y = y; + return this; + }, + + /** + * set the vector value without triggering the callback + * @name setMuted + * @memberOf me.ObservableVector2d + * @function + * @param {Number} x x value of the vector + * @param {Number} y y value of the vector + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + setMuted : function (x, y) { + this._x = x; + this._y = y; + return this; + }, + + /** + * set the callback to be executed when the vector is changed + * @name setCallback + * @memberOf me.ObservableVector2d + * @function + * @param {function} onUpdate callback + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + setCallback : function (fn) { + if (typeof(fn) !== "function") { + throw new me.ObservableVector2d.Error( + "invalid `onUpdate` callback" + ); + } + this.onUpdate = fn; + return this; + }, + + /** + * Add the passed vector to this vector + * @name add + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + add : function (v) { + return this._set(this._x + v.x, this._y + v.y); + }, + + /** + * Substract the passed vector to this vector + * @name sub + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + sub : function (v) { + return this._set(this._x - v.x, this._y - v.y); + }, + + /** + * Multiply this vector values by the given scalar + * @name scale + * @memberOf me.ObservableVector2d + * @function + * @param {Number} x + * @param {Number} [y=x] + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + scale : function (x, y) { + return this._set(this._x * x, this._y * (typeof (y) !== "undefined" ? y : x)); + }, + + /** + * Multiply this vector values by the passed vector + * @name scaleV + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + scaleV : function (v) { + return this._set(this._x * v.x, this._y * v.y); + }, + + /** + * Divide this vector values by the passed value + * @name div + * @memberOf me.ObservableVector2d + * @function + * @param {Number} value + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + div : function (n) { + return this._set(this._x / n, this._y / n); + }, + + /** + * Update this vector values to absolute values + * @name abs + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + abs : function () { + return this._set((this._x < 0) ? -this._x : this._x, (this._y < 0) ? -this._y : this._y); + }, + + /** + * Clamp the vector value within the specified value range + * @name clamp + * @memberOf me.ObservableVector2d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.ObservableVector2d} new me.ObservableVector2d + */ + clamp : function (low, high) { + return new me.ObservableVector2d(this.x.clamp(low, high), this.y.clamp(low, high), {onUpdate: this.onUpdate}); + }, + + /** + * Clamp this vector value within the specified value range + * @name clampSelf + * @memberOf me.ObservableVector2d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + clampSelf : function (low, high) { + return this._set(this._x.clamp(low, high), this._y.clamp(low, high)); + }, + + /** + * Update this vector with the minimum value between this and the passed vector + * @name minV + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + minV : function (v) { + return this._set((this._x < v.x) ? this._x : v.x, (this._y < v.y) ? this._y : v.y); + }, + + /** + * Update this vector with the maximum value between this and the passed vector + * @name maxV + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + maxV : function (v) { + return this._set((this._x > v.x) ? this._x : v.x, (this._y > v.y) ? this._y : v.y); + }, + + /** + * Floor the vector values + * @name floor + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} new me.ObservableVector2d + */ + floor : function () { + return new me.ObservableVector2d(Math.floor(this._x), Math.floor(this._y), {onUpdate: this.onUpdate}); + }, + + /** + * Floor this vector values + * @name floorSelf + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + floorSelf : function () { + return this._set(Math.floor(this._x), Math.floor(this._y)); + }, + + /** + * Ceil the vector values + * @name ceil + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} new me.ObservableVector2d + */ + ceil : function () { + return new me.ObservableVector2d(Math.ceil(this._x), Math.ceil(this._y), {onUpdate: this.onUpdate}); + }, + + /** + * Ceil this vector values + * @name ceilSelf + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + ceilSelf : function () { + return this._set(Math.ceil(this._x), Math.ceil(this._y)); + }, + + /** + * Negate the vector values + * @name negate + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} new me.ObservableVector2d + */ + negate : function () { + return new me.ObservableVector2d(-this._x, -this._y, {onUpdate: this.onUpdate}); + }, + + /** + * Negate this vector values + * @name negateSelf + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + negateSelf : function () { + return this._set(-this._x, -this._y); + }, + + /** + * Copy the x,y values of the passed vector to this one + * @name copy + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + copy : function (v) { + return this._set(v.x, v.y); + }, + + /** + * return true if the two vectors are the same + * @name equals + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {Boolean} + */ + equals : function (v) { + return ((this._x === v.x) && (this._y === v.y)); + }, + + /** + * normalize this vector (scale the vector so that its magnitude is 1) + * @name normalize + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + normalize : function () { + var d = this.length(); + if (d > 0) { + return this._set(this._x / d, this._y / d); + } + return this; + }, + + /** + * change this vector to be perpendicular to what it was before.
+ * (Effectively rotates it 90 degrees in a clockwise direction) + * @name perp + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + perp : function () { + return this._set(this._y, -this._x); + }, + + /** + * Rotate this vector (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.ObservableVector2d + * @function + * @param {number} angle The angle to rotate (in radians) + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + rotate : function (angle) { + var x = this._x; + var y = this._y; + return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle)); + }, + + /** + * return the dot product of this vector and the passed one + * @name dotProduct + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {Number} The dot product. + */ + dotProduct : function (v) { + return this._x * v.x + this._y * v.y; + }, + + /** + * return the distance between this vector and the passed one + * @name distance + * @memberOf me.ObservableVector2d + * @function + * @param {me.ObservableVector2d} v + * @return {Number} + */ + distance : function (v) { + return Math.sqrt((this._x - v.x) * (this._x - v.x) + (this._y - v.y) * (this._y - v.y)); + }, + + /** + * return a clone copy of this vector + * @name clone + * @memberOf me.ObservableVector2d + * @function + * @return {me.ObservableVector2d} new me.ObservableVector2d + */ + clone : function () { + // shall we return a cloned me.ObservableVector2d here ? + return new me.ObservableVector2d(this._x, this._y, {onUpdate: this.onUpdate}); + }, + + /** + * return a `me.Vector2d` copy of this `me.ObservableVector2d` object + * @name toVector2d + * @memberOf me.ObservableVector2d + * @function + * @return {me.Vector2d} new me.Vector2d + */ + toVector2d : function () { + return new me.Vector2d(this._x, this._y); + }, + + /** + * convert the object to a string representation + * @name toString + * @memberOf me.ObservableVector2d + * @function + * @return {String} + */ + toString : function () { + return "x:" + this._x + ",y:" + this._y; + } + }); + + /** + * Base class for Vector2d exception handling. + * @name Error + * @class + * @memberOf me.ObservableVector2d + * @constructor + * @param {String} msg Error message. + */ + me.ObservableVector2d.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.ObservableVector2d.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A Vector3d object that provide notification by executing the given callback when the vector is changed. + * @class + * @extends me.Vector3d + * @constructor + * @param {Number} [x=0] x value of the vector + * @param {Number} [y=0] y value of the vector + * @param {Number} [z=0] z value of the vector + * @param {Object} settings additional required parameters + * @param {Function} settings.onUpdate the callback to be executed when the vector is changed + */ + me.ObservableVector3d = me.Vector3d.extend({ + /** @scope me.ObservableVector3d.prototype */ + + /** + * @ignore + */ + init : function (x, y, z, settings) { + /** + * x value of the vector + * @public + * @type Number + * @name x + * @memberOf me.ObservableVector3d + */ + Object.defineProperty(this, "x", { + /** + * @ignore + */ + get : function () { + return this._x; + }, + /** + * @ignore + */ + set : function (value) { + this.onUpdate(value, this._y, this._z, this._x, this._y, this._z); + this._x = value; + } + }); + + /** + * y value of the vector + * @public + * @type Number + * @name y + * @memberOf me.ObservableVector3d + */ + Object.defineProperty(this, "y", { + /** + * @ignore + */ + get : function () { + return this._y; + }, + /** + * @ignore + */ + set : function (value) { + this.onUpdate(this._x, value, this._z, this._x, this._y, this._z); + this._y = value; + } + }); + + /** + * z value of the vector + * @public + * @type Number + * @name z + * @memberOf me.ObservableVector3d + */ + Object.defineProperty(this, "z", { + /** + * @ignore + */ + get : function () { + return this._z; + }, + /** + * @ignore + */ + set : function (value) { + this.onUpdate(this._x, this._y, value, this._x, this._y, this._z); + this._z = value; + } + }); + + if (typeof(settings) === "undefined") { + throw new me.ObservableVector3d.Error( + "undefined `onUpdate` callback" + ); + } + this.setCallback(settings.onUpdate); + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + }, + + /** + * @ignore */ + _set : function (x, y, z) { + this.onUpdate(x, y, z, this._x, this._y, this._z); + this._x = x; + this._y = y; + this._z = z; + return this; + }, + + /** + * set the vector value without triggering the callback + * @name setMuted + * @memberOf me.ObservableVector3d + * @function + * @param {Number} x x value of the vector + * @param {Number} y y value of the vector + * @param {Number} z z value of the vector + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + setMuted : function (x, y, z) { + this._x = x; + this._y = y; + this._z = z; + return this; + }, + + /** + * set the callback to be executed when the vector is changed + * @name setCallback + * @memberOf me.ObservableVector3d + * @function + * @param {function} onUpdate callback + * @return {me.ObservableVector2d} Reference to this object for method chaining + */ + setCallback : function (fn) { + if (typeof(fn) !== "function") { + throw new me.ObservableVector2d.Error( + "invalid `onUpdate` callback" + ); + } + this.onUpdate = fn; + return this; + }, + + /** + * Add the passed vector to this vector + * @name add + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + add : function (v) { + return this._set(this._x + v.x, this._y + v.y, this._z + (v.z || 0)); + }, + + /** + * Substract the passed vector to this vector + * @name sub + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + sub : function (v) { + return this._set(this._x - v.x, this._y - v.y, this._z - (v.z || 0)); + }, + + /** + * Multiply this vector values by the given scalar + * @name scale + * @memberOf me.ObservableVector3d + * @function + * @param {Number} x + * @param {Number} [y=x] + * @param {Number} [z=x] + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + scale : function (x, y, z) { + y = (typeof (y) !== "undefined" ? y : x); + z = (typeof (z) !== "undefined" ? z : x); + return this._set(this._x * x, this._y * y, this._z * z); + }, + + /** + * Multiply this vector values by the passed vector + * @name scaleV + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + scaleV : function (v) { + return this._set(this._x * v.x, this._y * v.y, this._z * (v.z || 1)); + }, + + /** + * Divide this vector values by the passed value + * @name div + * @memberOf me.ObservableVector3d + * @function + * @param {Number} value + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + div : function (n) { + return this._set(this._x / n, this._y / n, this._z / n); + }, + + /** + * Update this vector values to absolute values + * @name abs + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + abs : function () { + return this._set( + (this._x < 0) ? -this._x : this._x, + (this._y < 0) ? -this._y : this._y, + (this._Z < 0) ? -this._z : this._z + ); + }, + + /** + * Clamp the vector value within the specified value range + * @name clamp + * @memberOf me.ObservableVector3d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.ObservableVector3d} new me.ObservableVector3d + */ + clamp : function (low, high) { + return new me.ObservableVector3d( + this._x.clamp(low, high), + this._y.clamp(low, high), + this._z.clamp(low, high), + {onUpdate: this.onUpdate} + ); + }, + + /** + * Clamp this vector value within the specified value range + * @name clampSelf + * @memberOf me.ObservableVector3d + * @function + * @param {Number} low + * @param {Number} high + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + clampSelf : function (low, high) { + return this._set( + this._x.clamp(low, high), + this._y.clamp(low, high), + this._z.clamp(low, high) + ); + }, + + /** + * Update this vector with the minimum value between this and the passed vector + * @name minV + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + minV : function (v) { + var _vz = v.z || 0; + return this._set( + (this._x < v.x) ? this._x : v.x, + (this._y < v.y) ? this._y : v.y, + (this._z < _vz) ? this._z : _vz + ); + }, + + /** + * Update this vector with the maximum value between this and the passed vector + * @name maxV + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + maxV : function (v) { + var _vz = v.z || 0; + return this._set( + (this._x > v.x) ? this._x : v.x, + (this._y > v.y) ? this._y : v.y, + (this._z > _vz) ? this._z : _vz + ); + }, + + /** + * Floor the vector values + * @name floor + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} new me.ObservableVector3d + */ + floor : function () { + return new me.ObservableVector3d( + Math.floor(this._x), + Math.floor(this._y), + Math.floor(this._z), + {onUpdate: this.onUpdate} + ); + }, + + /** + * Floor this vector values + * @name floorSelf + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + floorSelf : function () { + return this._set(Math.floor(this._x), Math.floor(this._y), Math.floor(this._z)); + }, + + /** + * Ceil the vector values + * @name ceil + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} new me.ObservableVector3d + */ + ceil : function () { + return new me.ObservableVector3d( + Math.ceil(this._x), + Math.ceil(this._y), + Math.ceil(this._z), + {onUpdate: this.onUpdate} + ); + }, + + /** + * Ceil this vector values + * @name ceilSelf + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + ceilSelf : function () { + return this._set(Math.ceil(this._x), Math.ceil(this._y), Math.ceil(this._z)); + }, + + /** + * Negate the vector values + * @name negate + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} new me.ObservableVector3d + */ + negate : function () { + return new me.ObservableVector3d( + -this._x, + -this._y, + -this._z, + {onUpdate: this.onUpdate} + ); + }, + + /** + * Negate this vector values + * @name negateSelf + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + negateSelf : function () { + return this._set(-this._x, -this._y, -this._z); + }, + + /** + * Copy the x,y,z values of the passed vector to this one + * @name copy + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + copy : function (v) { + return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this._z); + }, + + /** + * return true if the two vectors are the same + * @name equals + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {Boolean} + */ + equals : function (v) { + return ((this._x === v.x) && (this._y === v.y) && (this._z === (v.z || this._z))); + }, + + /** + * normalize this vector (scale the vector so that its magnitude is 1) + * @name normalize + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + normalize : function () { + var d = this.length(); + if (d > 0) { + return this._set(this._x / d, this._y / d, this._z / d); + } + return this; + }, + + /** + * change this vector to be perpendicular to what it was before.
+ * (Effectively rotates it 90 degrees in a clockwise direction) + * @name perp + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + perp : function () { + return this._set(this._y, -this._x, this._z); + }, + + /** + * Rotate this vector (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.ObservableVector3d + * @function + * @param {number} angle The angle to rotate (in radians) + * @return {me.ObservableVector3d} Reference to this object for method chaining + */ + rotate : function (angle) { + var x = this._x; + var y = this._y; + return this._set( + x * Math.cos(angle) - y * Math.sin(angle), + x * Math.sin(angle) + y * Math.cos(angle), + this._z + ); + }, + + /** + * return the dot product of this vector and the passed one + * @name dotProduct + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {Number} The dot product. + */ + dotProduct : function (v) { + return this._x * v.x + this._y * v.y + this._z * (v.z || 1); + }, + + /** + * return the distance between this vector and the passed one + * @name distance + * @memberOf me.ObservableVector3d + * @function + * @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v + * @return {Number} + */ + distance : function (v) { + var dx = this._x - v.x, dy = this._y - v.y, dz = this._z - (v.z || 0); + return Math.sqrt(dx * dx + dy * dy + dz * dz); + }, + + /** + * return a clone copy of this vector + * @name clone + * @memberOf me.ObservableVector3d + * @function + * @return {me.ObservableVector3d} new me.ObservableVector3d + */ + clone : function () { + // shall we return a cloned me.ObservableVector3d here ? + return new me.ObservableVector3d( + this._x, + this._y, + this._z, + {onUpdate: this.onUpdate} + ); + }, + + /** + * return a `me.Vector3d` copy of this `me.ObservableVector3d` object + * @name toVector3d + * @memberOf me.ObservableVector3d + * @function + * @return {me.Vector3d} new me.Vector3d + */ + toVector3d : function () { + return new me.Vector3d(this._x, this._y, this._z); + }, + + /** + * convert the object to a string representation + * @name toString + * @memberOf me.ObservableVector3d + * @function + * @return {String} + */ + toString : function () { + return "x:" + this._x + ",y:" + this._y + ",z:" + this._z; + } + }); + + /** + * Base class for Vector3d exception handling. + * @name Error + * @class + * @memberOf me.ObservableVector3d + * @constructor + * @param {String} msg Error message. + */ + me.ObservableVector3d.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.ObservableVector3d.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a Matrix2d Object.
+ * the identity matrix and parameters position :
+ * + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {me.Matrix2d} [mat2d] An instance of me.Matrix2d to copy from + * @param {Number[]} [arguments...] Matrix elements. See {@link me.Matrix2d.set} + */ + me.Matrix2d = me.Object.extend( + /** @scope me.Matrix2d.prototype */ { + + /** @ignore */ + init : function () { + if (typeof(this.val) === "undefined") { + this.val = new Float32Array(9); + } + if (arguments.length && arguments[0] instanceof me.Matrix2d) { + this.copy(arguments[0]); + } + else if (arguments.length >= 6) { + this.setTransform.apply(this, arguments); + } + else { + this.onResetEvent(); + } + }, + + /** @ignore */ + onResetEvent : function() { + this.identity(); + }, + + /** + * reset the transformation matrix to the identity matrix (no transformation).
+ * the identity matrix and parameters position :
+ * + * @name identity + * @memberOf me.Matrix2d + * @function + * @return {me.Matrix2d} Reference to this object for method chaining + */ + identity : function () { + this.setTransform( + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + ); + return this; + }, + + /** + * set the matrix to the specified value + * @name setTransform + * @memberOf me.Matrix2d + * @function + * @param {Number} a + * @param {Number} b + * @param {Number} c + * @param {Number} d + * @param {Number} e + * @param {Number} f + * @param {Number} [g=0] + * @param {Number} [h=0] + * @param {Number} [i=1] + * @return {me.Matrix2d} Reference to this object for method chaining + */ + setTransform : function () { + var a = this.val; + + if (arguments.length === 9) { + a[0] = arguments[0]; // a - m00 + a[1] = arguments[1]; // b - m10 + a[2] = arguments[2]; // c - m20 + a[3] = arguments[3]; // d - m01 + a[4] = arguments[4]; // e - m11 + a[5] = arguments[5]; // f - m21 + a[6] = arguments[6]; // g - m02 + a[7] = arguments[7]; // h - m12 + a[8] = arguments[8]; // i - m22 + } else if (arguments.length === 6) { + a[0] = arguments[0]; // a + a[1] = arguments[2]; // c + a[2] = arguments[4]; // e + a[3] = arguments[1]; // b + a[4] = arguments[3]; // d + a[5] = arguments[5]; // f + a[6] = 0; // g + a[7] = 0; // h + a[8] = 1; // i + } + + return this; + }, + + /** + * Copies over the values from another me.Matrix2d. + * @name copy + * @memberOf me.Matrix2d + * @function + * @param {me.Matrix2d} m the matrix object to copy from + * @return {me.Matrix2d} Reference to this object for method chaining + */ + copy : function (b) { + this.val.set(b.val); + return this; + }, + + /** + * multiply both matrix + * @name multiply + * @memberOf me.Matrix2d + * @function + * @param {me.Matrix2d} b Other matrix + * @return {me.Matrix2d} Reference to this object for method chaining + */ + multiply : function (b) { + b = b.val; + var a = this.val, + a0 = a[0], + a1 = a[1], + a3 = a[3], + a4 = a[4], + b0 = b[0], + b1 = b[1], + b3 = b[3], + b4 = b[4], + b6 = b[6], + b7 = b[7]; + + a[0] = a0 * b0 + a3 * b1; + a[1] = a1 * b0 + a4 * b1; + a[3] = a0 * b3 + a3 * b4; + a[4] = a1 * b3 + a4 * b4; + a[6] += a0 * b6 + a3 * b7; + a[7] += a1 * b6 + a4 * b7; + + return this; + }, + + /** + * Transpose the value of this matrix. + * @name transpose + * @memberOf me.Matrix2d + * @function + * @return {me.Matrix2d} Reference to this object for method chaining + */ + transpose : function () { + var tmp, a = this.val; + + tmp = a[1]; + a[1] = a[3]; + a[3] = tmp; + tmp = a[2]; + a[2] = a[6]; + a[6] = tmp; + tmp = a[5]; + a[5] = a[7]; + a[7] = tmp; + + return this; + }, + + /** + * invert this matrix, causing it to apply the opposite transformation. + * @name invert + * @memberOf me.Matrix2d + * @function + * @return {me.Matrix2d} Reference to this object for method chaining + */ + invert : function () { + var val = this.val; + + var a = val[ 0 ], b = val[ 1 ], c = val[ 2 ], + d = val[ 3 ], e = val[ 4 ], f = val[ 5 ], + g = val[ 6 ], h = val[ 7 ], i = val[ 8 ]; + + var ta = i * e - f * h, + td = f * g - i * d, + tg = h * d - e * g; + + var n = a * ta + b * td + c * tg; + + val[ 0 ] = ta / n; + val[ 1 ] = ( c * h - i * b ) / n; + val[ 2 ] = ( f * b - c * e ) / n; + + val[ 3 ] = td / n; + val[ 4 ] = ( i * a - c * g ) / n; + val[ 5 ] = ( c * d - f * a ) / n; + + val[ 6 ] = tg / n; + val[ 7 ] = ( b * g - h * a ) / n; + val[ 8 ] = ( e * a - b * d ) / n; + + return this; + }, + + /** + * Transforms the given vector according to this matrix. + * @name multiplyVector + * @memberOf me.Matrix2d + * @function + * @param {me.Vector2d} vector the vector object to be transformed + * @return {me.Vector2d} result vector object. Useful for chaining method calls. + **/ + multiplyVector : function (v) { + var a = this.val, + x = v.x, + y = v.y; + + v.x = x * a[0] + y * a[3] + a[6]; + v.y = x * a[1] + y * a[4] + a[7]; + + return v; + }, + + /** + * scale the matrix + * @name scale + * @memberOf me.Matrix2d + * @function + * @param {Number} x a number representing the abscissa of the scaling vector. + * @param {Number} [y=x] a number representing the ordinate of the scaling vector. + * @return {me.Matrix2d} Reference to this object for method chaining + */ + scale : function (x, y) { + var a = this.val, + _x = x, + _y = typeof(y) === "undefined" ? _x : y; + + a[0] *= _x; + a[1] *= _x; + a[3] *= _y; + a[4] *= _y; + + return this; + }, + + /** + * adds a 2D scaling transformation. + * @name scaleV + * @memberOf me.Matrix2d + * @function + * @param {me.Vector2d} vector scaling vector + * @return {me.Matrix2d} Reference to this object for method chaining + */ + scaleV : function (v) { + return this.scale(v.x, v.y); + }, + + /** + * specifies a 2D scale operation using the [sx, 1] scaling vector + * @name scaleX + * @memberOf me.Matrix2d + * @function + * @param {Number} x x scaling vector + * @return {me.Matrix2d} Reference to this object for method chaining + */ + scaleX : function (x) { + return this.scale(x, 1); + }, + + /** + * specifies a 2D scale operation using the [1,sy] scaling vector + * @name scaleY + * @memberOf me.Matrix2d + * @function + * @param {Number} y y scaling vector + * @return {me.Matrix2d} Reference to this object for method chaining + */ + scaleY : function (y) { + return this.scale(1, y); + }, + + /** + * rotate the matrix (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.Matrix2d + * @function + * @param {Number} angle Rotation angle in radians. + * @return {me.Matrix2d} Reference to this object for method chaining + */ + rotate : function (angle) { + if (angle !== 0) { + var a = this.val, + a0 = a[0], + a1 = a[1], + a3 = a[3], + a4 = a[4], + s = Math.sin(angle), + c = Math.cos(angle); + + a[0] = a0 * c + a3 * s; + a[1] = a1 * c + a4 * s; + a[3] = a0 * -s + a3 * c; + a[4] = a1 * -s + a4 * c; + } + return this; + }, + + /** + * translate the matrix position on the horizontal and vertical axis + * @name translate + * @memberOf me.Matrix2d + * @function + * @param {Number} x the x coordindates to translate the matrix by + * @param {Number} y the y coordindates to translate the matrix by + * @return {me.Matrix2d} Reference to this object for method chaining + */ + translate : function (x, y) { + var a = this.val; + + a[6] += a[0] * x + a[3] * y; + a[7] += a[1] * x + a[4] * y; + + return this; + }, + + /** + * translate the matrix by a vector on the horizontal and vertical axis + * @name translateV + * @memberOf me.Matrix2d + * @function + * @param {me.Vector2d} v the vector to translate the matrix by + * @return {me.Matrix2d} Reference to this object for method chaining + */ + translateV : function (v) { + return this.translate(v.x, v.y); + }, + + /** + * returns true if the matrix is an identity matrix. + * @name isIdentity + * @memberOf me.Matrix2d + * @function + * @return {Boolean} + **/ + isIdentity : function () { + var a = this.val; + + return ( + a[0] === 1 && + a[1] === 0 && + a[2] === 0 && + a[3] === 0 && + a[4] === 1 && + a[5] === 0 && + a[6] === 0 && + a[7] === 0 && + a[8] === 1 + ); + }, + + /** + * Clone the Matrix + * @name clone + * @memberOf me.Matrix2d + * @function + * @return {me.Matrix2d} + */ + clone : function () { + return me.pool.pull("me.Matrix2d").copy(this); + }, + + /** + * convert the object to a string representation + * @name toString + * @memberOf me.Matrix2d + * @function + * @return {String} + */ + toString : function () { + var a = this.val; + + return "me.Matrix2d(" + + a[0] + ", " + a[1] + ", " + a[2] + ", " + + a[3] + ", " + a[4] + ", " + a[5] + ", " + + a[6] + ", " + a[7] + ", " + a[8] + + ")"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * an ellipse Object + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Number} x the center x coordinate of the ellipse + * @param {Number} y the center y coordinate of the ellipse + * @param {Number} w width (diameter) of the ellipse + * @param {Number} h height (diameter) of the ellipse + */ + me.Ellipse = me.Object.extend( + { + /** @scope me.Ellipse.prototype */ + /** @ignore */ + init : function (x, y, w, h) { + /** + * the center coordinates of the ellipse + * @public + * @type {me.Vector2d} + * @name pos + * @memberOf me.Ellipse + */ + this.pos = new me.Vector2d(); + + /** + * The bounding rectangle for this shape + * @private + * @type {me.Rect} + * @name _bounds + * @memberOf me.Ellipse + */ + this._bounds = undefined; + + /** + * Maximum radius of the ellipse + * @public + * @type {Number} + * @name radius + * @memberOf me.Ellipse + */ + this.radius = NaN; + + /** + * Pre-scaled radius vector for ellipse + * @public + * @type {me.Vector2d} + * @name radiusV + * @memberOf me.Ellipse + */ + this.radiusV = new me.Vector2d(); + + /** + * Radius squared, for pythagorean theorom + * @public + * @type {me.Vector2d} + * @name radiusSq + * @memberOf me.Ellipse + */ + this.radiusSq = new me.Vector2d(); + + /** + * x/y scaling ratio for ellipse + * @public + * @type {me.Vector2d} + * @name ratio + * @memberOf me.Ellipse + */ + this.ratio = new me.Vector2d(); + + // the shape type + this.shapeType = "Ellipse"; + this.setShape(x, y, w, h); + }, + + /** + * set new value to the Ellipse shape + * @name setShape + * @memberOf me.Ellipse + * @function + * @param {Number} x position of the ellipse + * @param {Number} y position of the ellipse + * @param {Number} w width (diameter) of the ellipse + * @param {Number} h height (diameter) of the ellipse + */ + setShape : function (x, y, w, h) { + var hW = w / 2; + var hH = h / 2; + this.pos.set(x, y); + this.radius = Math.max(hW, hH); + this.ratio.set(hW / this.radius, hH / this.radius); + this.radiusV.set(this.radius, this.radius).scaleV(this.ratio); + var r = this.radius * this.radius; + this.radiusSq.set(r, r).scaleV(this.ratio); + this.updateBounds(); + return this; + }, + + /** + * Rotate this Ellipse (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.Ellipse + * @function + * @param {Number} angle The angle to rotate (in radians) + * @return {me.Ellipse} Reference to this object for method chaining + */ + rotate : function (/*angle*/) { + // TODO + return this; + }, + + /** + * Scale this Ellipse by the specified scalar. + * @name scale + * @memberOf me.Ellipse + * @function + * @param {Number} x + * @param {Number} [y=x] + * @return {me.Ellipse} Reference to this object for method chaining + */ + scale : function (x, y) { + y = typeof (y) !== "undefined" ? y : x; + return this.setShape( + this.pos.x, + this.pos.y, + this.radiusV.x * 2 * x, + this.radiusV.y * 2 * y + ); + }, + + /** + * Scale this Ellipse by the specified vector. + * @name scale + * @memberOf me.Ellipse + * @function + * @param {me.Vector2d} v + * @return {me.Ellipse} Reference to this object for method chaining + */ + scaleV : function (v) { + return this.scale(v.x, v.y); + }, + + /** + * apply the given transformation matrix to this ellipse + * @name transform + * @memberOf me.Ellipse + * @function + * @param {me.Matrix2d} matrix the transformation matrix + * @return {me.Polygon} Reference to this object for method chaining + */ + transform : function (/* m */) { + // TODO + return this; + }, + + /** + * translate the circle/ellipse by the specified offset + * @name translate + * @memberOf me.Ellipse + * @function + * @param {Number} x x offset + * @param {Number} y y offset + * @return {me.Ellipse} this ellipse + */ + translate : function (x, y) { + this.pos.x += x; + this.pos.y += y; + this._bounds.translate(x, y); + return this; + }, + + /** + * translate the circle/ellipse by the specified vector + * @name translateV + * @memberOf me.Ellipse + * @function + * @param {me.Vector2d} v vector offset + * @return {me.Rect} this ellipse + */ + translateV : function (v) { + this.pos.add(v); + this._bounds.translateV(v); + return this; + }, + + /** + * check if this circle/ellipse contains the specified point + * @name containsPointV + * @memberOf me.Ellipse + * @function + * @param {me.Vector2d} point + * @return {boolean} true if contains + */ + containsPointV: function (v) { + return this.containsPoint(v.x, v.y); + }, + + /** + * check if this circle/ellipse contains the specified point + * @name containsPoint + * @memberOf me.Ellipse + * @function + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @return {boolean} true if contains + */ + containsPoint: function (x, y) { + // Make position relative to object center point. + x -= this.pos.x; + y -= this.pos.y; + // Pythagorean theorem. + return ( + ((x * x) / this.radiusSq.x) + + ((y * y) / this.radiusSq.y) + ) <= 1.0; + }, + + /** + * returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. + * @name getBounds + * @memberOf me.Ellipse + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + getBounds : function () { + return this._bounds; + }, + + /** + * update the bounding box for this shape. + * @name updateBounds + * @memberOf me.Ellipse + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + updateBounds : function () { + var rx = this.radiusV.x, + ry = this.radiusV.y, + x = this.pos.x - rx, + y = this.pos.y - ry, + w = rx * 2, + h = ry * 2; + + if (!this._bounds) { + this._bounds = new me.Rect(x, y, w, h); + } else { + this._bounds.setShape(x, y, w, h); + } + return this._bounds; + }, + + /** + * clone this Ellipse + * @name clone + * @memberOf me.Ellipse + * @function + * @return {me.Ellipse} new Ellipse + */ + clone : function () { + return new me.Ellipse( + this.pos.x, + this.pos.y, + this.radiusV.x * 2, + this.radiusV.y * 2 + ); + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a polygon Object.
+ * Please do note that melonJS implements a simple Axis-Aligned Boxes collision algorithm, which requires all polygons used for collision to be convex with all vertices defined with clockwise winding. + * A polygon is convex when all line segments connecting two points in the interior do not cross any edge of the polygon + * (which means that all angles are less than 180 degrees), as described here below :
+ *


+ * A polygon's `winding` is clockwise iff its vertices (points) are declared turning to the right. The image above shows COUNTERCLOCKWISE winding. + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Number} x origin point of the Polygon + * @param {Number} y origin point of the Polygon + * @param {me.Vector2d[]} points array of vector defining the Polygon + */ + me.Polygon = me.Object.extend( + /** @scope me.Polygon.prototype */ { + + /** @ignore */ + init : function (x, y, points) { + /** + * origin point of the Polygon + * @public + * @type {me.Vector2d} + * @name pos + * @memberOf me.Polygon + */ + this.pos = new me.Vector2d(); + + /** + * The bounding rectangle for this shape + * @ignore + * @type {me.Rect} + * @name _bounds + * @memberOf me.Polygon + */ + this._bounds = undefined; + + /** + * Array of points defining the Polygon
+ * Note: If you manually change `points`, you **must** call `recalc`afterwards so that the changes get applied correctly. + * @public + * @type {me.Vector2d[]} + * @name points + * @memberOf me.Polygon + */ + this.points = null; + + // the shape type + this.shapeType = "Polygon"; + this.setShape(x, y, points); + }, + + /** + * set new value to the Polygon + * @name setShape + * @memberOf me.Polygon + * @function + * @param {Number} x position of the Polygon + * @param {Number} y position of the Polygon + * @param {me.Vector2d[]} points array of vector defining the Polygon + */ + setShape : function (x, y, points) { + this.pos.set(x, y); + this.points = points; + this.recalc(); + this.updateBounds(); + return this; + }, + + /** + * apply the given transformation matrix to this Polygon + * @name transform + * @memberOf me.Polygon + * @function + * @param {me.Matrix2d} matrix the transformation matrix + * @return {me.Polygon} Reference to this object for method chaining + */ + transform : function (m) { + var points = this.points; + var len = points.length; + for (var i = 0; i < len; i++) { + m.multiplyVector(points[i]); + } + this.recalc(); + this.updateBounds(); + return this; + }, + + /** + * apply an isometric projection to this shape + * @name toIso + * @memberOf me.Polygon + * @function + * @return {me.Polygon} Reference to this object for method chaining + */ + toIso : function () { + return this.rotate(Math.PI / 4).scale(Math.SQRT2, Math.SQRT1_2); + }, + + /** + * apply a 2d projection to this shape + * @name to2d + * @memberOf me.Polygon + * @function + * @return {me.Polygon} Reference to this object for method chaining + */ + to2d : function () { + return this.scale(Math.SQRT1_2, Math.SQRT2).rotate(-Math.PI / 4); + }, + + /** + * Rotate this Polygon (counter-clockwise) by the specified angle (in radians). + * @name rotate + * @memberOf me.Polygon + * @function + * @param {Number} angle The angle to rotate (in radians) + * @return {me.Polygon} Reference to this object for method chaining + */ + rotate : function (angle) { + if (angle !== 0) { + var points = this.points; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i].rotate(angle); + } + this.recalc(); + this.updateBounds(); + } + return this; + }, + + /** + * Scale this Polygon by the given scalar. + * @name scale + * @memberOf me.Polygon + * @function + * @param {Number} x + * @param {Number} [y=x] + * @return {me.Polygon} Reference to this object for method chaining + */ + scale : function (x, y) { + y = typeof (y) !== "undefined" ? y : x; + + var points = this.points; + var len = points.length; + for (var i = 0; i < len; i++) { + points[i].scale(x, y); + } + this.recalc(); + this.updateBounds(); + return this; + }, + + /** + * Scale this Polygon by the given vector + * @name scaleV + * @memberOf me.Polygon + * @function + * @param {me.Vector2d} v + * @return {me.Polygon} Reference to this object for method chaining + */ + scaleV : function (v) { + return this.scale(v.x, v.y); + }, + + /** + * Computes the calculated collision polygon. + * This **must** be called if the `points` array, `angle`, or `offset` is modified manually. + * @name recalc + * @memberOf me.Polygon + * @function + * @return {me.Polygon} Reference to this object for method chaining + */ + recalc : function () { + var i; + // The edges here are the direction of the `n`th edge of the polygon, relative to + // the `n`th point. If you want to draw a given edge from the edge value, you must + // first translate to the position of the starting point. + var edges = this.edges = []; + // The normals here are the direction of the normal for the `n`th edge of the polygon, relative + // to the position of the `n`th point. If you want to draw an edge normal, you must first + // translate to the position of the starting point. + var normals = this.normals = []; + // Copy the original points array and apply the offset/angle + var points = this.points; + var len = points.length; + + if (len < 3) { + throw new me.Polygon.Error("Requires at least 3 points"); + } + + // Calculate the edges/normals + for (i = 0; i < len; i++) { + var e = new me.Vector2d().copy(points[(i + 1) % len]).sub(points[i]); + edges.push(e); + normals.push(new me.Vector2d().copy(e).perp().normalize()); + } + return this; + }, + + /** + * translate the Polygon by the specified offset + * @name translate + * @memberOf me.Polygon + * @function + * @param {Number} x x offset + * @param {Number} y y offset + * @return {me.Polygon} this Polygon + */ + translate : function (x, y) { + this.pos.x += x; + this.pos.y += y; + this._bounds.translate(x, y); + return this; + }, + + /** + * translate the Polygon by the specified vector + * @name translateV + * @memberOf me.Polygon + * @function + * @param {me.Vector2d} v vector offset + * @return {me.Polygon} Reference to this object for method chaining + */ + translateV : function (v) { + this.pos.add(v); + this._bounds.translateV(v); + return this; + }, + + /** + * check if this Polygon contains the specified point + * @name containsPointV + * @memberOf me.Polygon + * @function + * @param {me.Vector2d} point + * @return {boolean} true if contains + */ + containsPointV: function (v) { + return this.containsPoint(v.x, v.y); + }, + + /** + * check if this Polygon contains the specified point
+ * (Note: it is highly recommended to first do a hit test on the corresponding
+ * bounding rect, as the function can be highly consuming with complex shapes) + * @name containsPoint + * @memberOf me.Polygon + * @function + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @return {boolean} true if contains + */ + containsPoint: function (x, y) { + var intersects = false; + var posx = this.pos.x, posy = this.pos.y; + var points = this.points; + var len = points.length; + + //http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + for (var i = 0, j = len - 1; i < len; j = i++) { + var iy = points[i].y + posy, ix = points[i].x + posx, + jy = points[j].y + posy, jx = points[j].x + posx; + if (((iy > y) !== (jy > y)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix)) { + intersects = !intersects; + } + } + return intersects; + }, + + /** + * returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. + * @name getBounds + * @memberOf me.Polygon + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + getBounds : function () { + return this._bounds; + }, + + /** + * update the bounding box for this shape. + * @name updateBounds + * @memberOf me.Polygon + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + updateBounds : function () { + if (!this._bounds) { + this._bounds = new me.Rect(0, 0, 0, 0); + } + this._bounds.setPoints(this.points); + this._bounds.translateV(this.pos); + + return this._bounds; + }, + + /** + * clone this Polygon + * @name clone + * @memberOf me.Polygon + * @function + * @return {me.Polygon} new Polygon + */ + clone : function () { + var copy = []; + this.points.forEach(function (point) { + copy.push(new me.Vector2d(point.x, point.y)); + }); + return new me.Polygon(this.pos.x, this.pos.y, copy); + } + }); + + /** + * Base class for Polygon exception handling. + * @name Error + * @class + * @memberOf me.Polygon + * @constructor + * @param {String} msg Error message. + */ + me.Polygon.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Polygon.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a rectangle Object + * @class + * @extends me.Polygon + * @memberOf me + * @constructor + * @param {Number} x position of the Rectangle + * @param {Number} y position of the Rectangle + * @param {Number} w width of the rectangle + * @param {Number} h height of the rectangle + */ + me.Rect = me.Polygon.extend( + /** @scope me.Rect.prototype */ { + + /** @ignore */ + init : function (x, y, w, h) { + + this.pos = new me.Vector2d(); + + // pre-allocate the vector array + this.points = [ + new me.Vector2d(), new me.Vector2d(), + new me.Vector2d(), new me.Vector2d() + ]; + + this.shapeType = "Rectangle"; + this.setShape(x, y, w, h); + }, + + /** + * set new value to the rectangle shape + * @name setShape + * @memberOf me.Rect + * @function + * @param {Number} x position of the Rectangle + * @param {Number} y position of the Rectangle + * @param {Number} w width of the rectangle + * @param {Number} h height of the rectangle + * @return {me.Rect} this rectangle + */ + setShape : function (x, y, w, h) { + + this.points[0].set(0, 0); // 0, 0 + this.points[1].set(w, 0); // 1, 0 + this.points[2].set(w, h); // 1, 1 + this.points[3].set(0, h); // 0, 1 + + me.Polygon.prototype.setShape.apply(this, [x, y, this.points]); + + // private properties to cache w & h + this._width = w; + this._height = h; + + return this; + }, + + /** + * resize the rectangle + * @name resize + * @memberOf me.Rect + * @function + * @param {Number} w new width of the rectangle + * @param {Number} h new height of the rectangle + * @return {me.Rect} this rectangle + */ + resize : function (w, h) { + this.width = w; + this.height = h; + return this; + }, + + /** + * returns the bounding box for this shape, the smallest rectangle object completely containing this shape. + * @name getBounds + * @memberOf me.Rect + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + getBounds : function () { + return this; + }, + + /** + * resize the rectangle to contain all the given points coordinates. + * @name setPoints + * @memberOf me.Rect + * @function + * @param {me.Vector2d[]} points array of vector defining a shape + * @return {me.Rect} this shape bounding box Rectangle object + */ + setPoints : function (points) { + var x = Infinity, y = Infinity, right = -Infinity, bottom = -Infinity; + points.forEach(function (point) { + x = Math.min(x, point.x); + y = Math.min(y, point.y); + right = Math.max(right, point.x); + bottom = Math.max(bottom, point.y); + }); + this.setShape(x, y, right - x, bottom - y); + return this; + }, + + /** + * Computes the calculated collision polygon. + * This **must** be called if the `points` array is modified manually. + * @ignore + * @name recalc + * @memberOf me.Rect + * @function + */ + recalc : function () { + me.Polygon.prototype.recalc.apply(this); + this._width = this.points[2].x; + this._height = this.points[2].y; + return this; + }, + + /** + * update the bounding box for this shape. + * @name updateBounds + * @memberOf me.Rect + * @function + * @return {me.Rect} this shape bounding box Rectangle object + */ + updateBounds : function () { + return this; + }, + + /** + * clone this rectangle + * @name clone + * @memberOf me.Rect + * @function + * @return {me.Rect} new rectangle + */ + clone : function () { + return new me.Rect(this.pos.x, this.pos.y, this._width, this._height); + }, + + /** + * copy the position and size of the given rectangle into this one + * @name copy + * @memberOf me.Rect + * @function + * @param {me.Rect} rect Source rectangle + * @return {me.Rect} new rectangle + */ + copy : function (rect) { + return this.setShape(rect.pos.x, rect.pos.y, rect._width, rect._height); + }, + + /** + * translate the rect by the specified offset + * @name translate + * @memberOf me.Rect + * @function + * @param {Number} x x offset + * @param {Number} y y offset + * @return {me.Rect} this rectangle + */ + translate : function (x, y) { + this.pos.x += x; + this.pos.y += y; + return this; + }, + + /** + * translate the rect by the specified vector + * @name translateV + * @memberOf me.Rect + * @function + * @param {me.Vector2d} v vector offset + * @return {me.Rect} this rectangle + */ + translateV : function (v) { + return this.translate(v.x, v.y); + }, + + /** + * merge this rectangle with another one + * @name union + * @memberOf me.Rect + * @function + * @param {me.Rect} rect other rectangle to union with + * @return {me.Rect} the union(ed) rectangle + */ + union : function (/** {me.Rect} */ r) { + var x1 = Math.min(this.left, r.left); + var y1 = Math.min(this.top, r.top); + + this.resize( + Math.max(this.right, r.right) - x1, + Math.max(this.bottom, r.bottom) - y1 + ); + + this.pos.set(x1, y1); + + return this; + }, + + /** + * check if this rectangle is intersecting with the specified one + * @name overlaps + * @memberOf me.Rect + * @function + * @param {me.Rect} rect + * @return {boolean} true if overlaps + */ + overlaps : function (r) { + return ( + this.left < r.right && + r.left < this.right && + this.top < r.bottom && + r.top < this.bottom + ); + }, + + /** + * check if this rectangle contains the specified one + * @name contains + * @memberOf me.Rect + * @function + * @param {me.Rect} rect + * @return {boolean} true if contains + */ + contains: function (r) { + return ( + r.left >= this.left && + r.right <= this.right && + r.top >= this.top && + r.bottom <= this.bottom + ); + }, + + /** + * check if this rectangle contains the specified point + * @name containsPoint + * @memberOf me.Rect + * @function + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @return {boolean} true if contains + */ + containsPoint: function (x, y) { + return ( + x >= this.left && + x <= this.right && + y >= this.top && + y <= this.bottom + ); + }, + + /** + * Returns a polygon whose edges are the same as this box. + * @name toPolygon + * @memberOf me.Rect + * @function + * @return {me.Polygon} a new Polygon that represents this rectangle. + */ + toPolygon: function () { + return new me.Polygon( + this.pos.x, this.pos.y, this.points + ); + } + }); + + // redefine some properties to ease our life when getting the rectangle coordinates + + /** + * left coordinate of the Rectangle + * @public + * @type {Number} + * @name left + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "left", { + /** + * @ignore + */ + get : function () { + return this.pos.x; + }, + configurable : true + }); + + /** + * right coordinate of the Rectangle + * @public + * @type {Number} + * @name right + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "right", { + /** + * @ignore + */ + get : function () { + var w = this._width; + return (this.pos.x + w) || w; + }, + configurable : true + }); + + /** + * top coordinate of the Rectangle + * @public + * @type {Number} + * @name top + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "top", { + /** + * @ignore + */ + get : function () { + return this.pos.y; + }, + configurable : true + }); + + /** + * bottom coordinate of the Rectangle + * @public + * @type {Number} + * @name bottom + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "bottom", { + /** + * @ignore + */ + get : function () { + var h = this._height; + return (this.pos.y + h) || h; + }, + configurable : true + }); + + /** + * width of the Rectangle + * @public + * @type {Number} + * @name width + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "width", { + /** + * @ignore + */ + get : function () { + return this._width; + }, + /** + * @ignore + */ + set : function (value) { + this.points[1].x = this.points[2].x = value; + // _width updated in recalc + this.recalc(); + }, + configurable : true + }); + + /** + * height of the Rectangle + * @public + * @type {Number} + * @name height + * @memberOf me.Rect + */ + Object.defineProperty(me.Rect.prototype, "height", { + /** + * @ignore + */ + get : function () { + return this._height; + }, + /** + * @ignore + */ + set : function (value) { + this.points[2].y = this.points[3].y = value; + // _height updated in recalc + this.recalc(); + }, + configurable : true + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a line segment Object.
+ * @class + * @extends me.Polygon + * @memberOf me + * @constructor + * @param {Number} x origin point of the Line + * @param {Number} y origin point of the Line + * @param {me.Vector2d[]} points array of vectors defining the Line + */ + me.Line = me.Polygon.extend( + /** @scope me.Line.prototype */ { + + /** + * check if this line segment contains the specified point + * @name containsPointV + * @memberOf me.Line + * @function + * @param {me.Vector2d} point + * @return {boolean} true if contains + */ + containsPointV: function (v) { + return this.containsPoint(v.x, v.y); + }, + + /** + * check if this line segment contains the specified point + * @name containsPoint + * @memberOf me.Line + * @function + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @return {boolean} true if contains + */ + containsPoint: function (x, y) { + // translate the given coordinates, + // rather than creating temp translated vectors + x -= this.pos.x; // Cx + y -= this.pos.y; // Cy + var start = this.points[0]; // Ax/Ay + var end = this.points[1]; // Bx/By + + //(Cy - Ay) * (Bx - Ax) = (By - Ay) * (Cx - Ax) + return (y - start.y) * (end.x - start.x) === (end.y - start.y) * (x - start.x); + }, + + /** + * Computes the calculated collision edges and normals. + * This **must** be called if the `points` array, `angle`, or `offset` is modified manually. + * @name recalc + * @memberOf me.Line + * @function + */ + recalc : function () { + // The edges here are the direction of the `n`th edge of the polygon, relative to + // the `n`th point. If you want to draw a given edge from the edge value, you must + // first translate to the position of the starting point. + var edges = this.edges = []; + // The normals here are the direction of the normal for the `n`th edge of the polygon, relative + // to the position of the `n`th point. If you want to draw an edge normal, you must first + // translate to the position of the starting point. + var normals = this.normals = []; + // Copy the original points array and apply the offset/angle + var points = this.points; + + if (points.length !== 2) { + throw new me.Line.Error("Requires exactly 2 points"); + } + + // Calculate the edges/normals + var e = new me.Vector2d().copy(points[1]).sub(points[0]); + edges.push(e); + normals.push(new me.Vector2d().copy(e).perp().normalize()); + + return this; + }, + + /** + * clone this line segment + * @name clone + * @memberOf me.Line + * @function + * @return {me.Line} new Line + */ + clone : function () { + var copy = []; + this.points.forEach(function (point) { + copy.push(new me.Vector2d(point.x, point.y)); + }); + return new me.Line(this.pos.x, this.pos.y, copy); + } + }); + + /** + * Base class for Line exception handling. + * @name Error + * @class + * @memberOf me.Line + * @constructor + * @param {String} msg Error message. + */ + me.Line.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Line.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +(function () { + + /** + * a Generic Body Object
+ * @class + * @extends me.Rect + * @memberOf me + * @constructor + * @param {me.Entity} entity the parent entity + * @param {me.Rect[]|me.Polygon[]|me.Line[]|me.Ellipse[]} [shapes] the initial list of shapes + */ + me.Body = me.Rect.extend( + /** @scope me.Body.prototype */ + { + /** @ignore */ + init : function (entity, shapes) { + + /** + * reference to the parent entity + * @ignore + */ + this.entity = entity; + + /** + * The collision shapes of the entity
+ * @ignore + * @type {me.Polygon[]|me.Line[]|me.Ellipse[]} + * @name shapes + * @memberOf me.Body + */ + this.shapes = []; + + /** + * The body collision mask, that defines what should collide with what.
+ * (by default will collide with all entities) + * @ignore + * @type Number + * @default me.collision.types.ALL_OBJECT + * @name collisionMask + * @see me.collision.types + * @memberOf me.Body + */ + this.collisionMask = me.collision.types.ALL_OBJECT; + + /** + * define the collision type of the body for collision filtering + * @public + * @type Number + * @default me.collision.types.ENEMY_OBJECT + * @name collisionType + * @see me.collision.types + * @memberOf me.Body + * @example + * // set the entity body collision type + * myEntity.body.collisionType = me.collision.types.PLAYER_OBJECT; + */ + this.collisionType = me.collision.types.ENEMY_OBJECT; + + /** + * entity current velocity
+ * @public + * @type me.Vector2d + * @default <0,0> + * @name vel + * @memberOf me.Body + */ + if (typeof(this.vel) === "undefined") { + this.vel = new me.Vector2d(); + } + this.vel.set(0, 0); + + /** + * entity current acceleration
+ * @public + * @type me.Vector2d + * @default <0,0> + * @name accel + * @memberOf me.Body + */ + if (typeof(this.accel) === "undefined") { + this.accel = new me.Vector2d(); + } + this.accel.set(0, 0); + + /** + * entity current friction
+ * @public + * @type me.Vector2d + * @default <0,0> + * @name friction + * @memberOf me.Body + */ + if (typeof(this.friction) === "undefined") { + this.friction = new me.Vector2d(); + } + this.friction.set(0, 0); + + /** + * max velocity (to limit entity velocity)
+ * @public + * @type me.Vector2d + * @default <1000,1000> + * @name maxVel + * @memberOf me.Body + */ + if (typeof(this.maxVel) === "undefined") { + this.maxVel = new me.Vector2d(); + } + this.maxVel.set(1000, 1000); + + /** + * Default gravity value of the entity
+ * to be set to 0 for RPG, shooter, etc...
+ * Note: Gravity can also globally be defined through me.sys.gravity + * @public + * @see me.sys.gravity + * @type Number + * @default 0.98 + * @name gravity + * @memberOf me.Body + */ + this.gravity = typeof(me.sys.gravity) !== "undefined" ? me.sys.gravity : 0.98; + + /** + * falling state of the object
+ * true if the object is falling
+ * false if the object is standing on something
+ * @readonly + * @public + * @type Boolean + * @default false + * @name falling + * @memberOf me.Body + */ + this.falling = false; + + /** + * jumping state of the object
+ * equal true if the entity is jumping
+ * @readonly + * @public + * @type Boolean + * @default false + * @name jumping + * @memberOf me.Body + */ + this.jumping = false; + + // call the super constructor + me.Rect.prototype.init.apply(this, [ + 0, + 0, + entity.width, + entity.height + ] + ); + + // parses the given shapes array and add them + for (var s = 0; s < shapes.length; s++) { + this.addShape(shapes[s].clone(), true); + } + }, + + /** + * add a collision shape to this entity
+ * (note: me.Rect objects will be converted to me.Polygon before being added) + * @name addShape + * @memberOf me.Body + * @public + * @function + * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object + * @return {Number} the shape array length + */ + addShape : function (shape, batchInsert) { + if (shape instanceof me.Rect) { + this.shapes.push(shape.toPolygon()); + } else { + // else polygon or circle + this.shapes.push(shape); + } + + if (batchInsert !== true) { + // update the body bounds to take in account the added shape + this.updateBounds(); + } + + // return the length of the shape list + return this.shapes.length; + }, + + /** + * add collision shapes based on the given PhysicsEditor JSON object + * @name addShapesFromJSON + * @memberOf me.Body + * @public + * @function + * @param {Object} json a JSON object as exported from the PhysicsEditor tool + * @param {String} id the shape identifier within the given the json object + * @param {String} [scale=1] the desired scale of the body (physic-body-editor only) + * @see https://www.codeandweb.com/physicseditor + * @return {Number} the shape array length + */ + addShapesFromJSON : function (json, id, scale) { + var data; + scale = scale || 1; + + // identify the json format + if (typeof(json.rigidBodies) === "undefined") { + // Physic Editor Format (https://www.codeandweb.com/physicseditor) + data = json[id]; + + if (typeof(data) === "undefined") { + throw new me.Body.Error("Identifier (" + id + ") undefined for the given PhysicsEditor JSON object)"); + } + + // go through all shapes and add them to the body + for (var i = 0; i < data.length; i++) { + var points = []; + for (var s = 0; s < data[i].shape.length; s += 2) { + points.push(new me.Vector2d(data[i].shape[s], data[i].shape[s + 1])); + } + this.addShape(new me.Polygon(0, 0, points), true); + } + } else { + // Physic Body Editor Format (http://www.aurelienribon.com/blog/projects/physics-body-editor/) + json.rigidBodies.forEach(function (shape) { + if (shape.name === id) { + data = shape; + // how to stop a forEach loop? + } + }); + + if (typeof(data) === "undefined") { + throw new me.Body.Error("Identifier (" + id + ") undefined for the given PhysicsEditor JSON object)"); + } + + // shapes origin point + // top-left origin in the editor is (0,1) + this.pos.set(data.origin.x, 1.0 - data.origin.y).scale(scale); + + var self = this; + // parse all polygons + data.polygons.forEach(function (poly) { + var points = []; + poly.forEach(function (point) { + // top-left origin in the editor is (0,1) + points.push(new me.Vector2d(point.x, 1.0 - point.y).scale(scale)); + }); + self.addShape(new me.Polygon(0, 0, points), true); + }); + // parse all circles + data.circles.forEach(function (circle) { + self.addShape(new me.Ellipse( + circle.cx * scale, + (1.0 - circle.cy) * scale, + circle.r * 2 * scale, + circle.r * 2 * scale + ), true); + }); + } + + // update the body bounds to take in account the added shapes + this.updateBounds(); + + // return the length of the shape list + return this.shapes.length; + }, + + /** + * return the collision shape at the given index + * @name getShape + * @memberOf me.Body + * @public + * @function + * @param {Number} [index=0] the shape object at the specified index + * @return {me.Polygon|me.Line|me.Ellipse} shape a shape object if defined + */ + getShape : function (index) { + return this.shapes[index || 0]; + }, + + /** + * remove the specified shape from the body shape list + * @name removeShape + * @memberOf me.Body + * @public + * @function + * @param {me.Polygon|me.Line|me.Ellipse} shape a shape object + * @return {Number} the shape array length + */ + removeShape : function (shape) { + this.shapes.remove(shape); + + // update the body bounds to take in account the removed shape + this.updateBounds(); + + // return the length of the shape list + return this.shapes.length; + }, + + /** + * remove the shape at the given index from the body shape list + * @name removeShapeAt + * @memberOf me.Body + * @public + * @function + * @param {Number} index the shape object at the specified index + * @return {Number} the shape array length + */ + removeShapeAt : function (index) { + return this.removeShape(this.getShape(index)); + }, + + /** + * By default all entities are able to collide with all other entities,
+ * but it's also possible to specificy 'collision filters' to provide a finer
+ * control over which entities can collide with each other. + * @name setCollisionMask + * @memberOf me.Body + * @public + * @function + * @see me.collision.types + * @param {Number} bitmask the collision mask + * @example + * // filter collision detection with collision shapes, enemies and collectables + * myEntity.body.setCollisionMask(me.collision.types.WORLD_SHAPE | me.collision.types.ENEMY_OBJECT | me.collision.types.COLLECTABLE_OBJECT); + * ... + * // disable collision detection with all other objects + * myEntity.body.setCollisionMask(me.collision.types.NO_OBJECT); + */ + setCollisionMask : function (bitmask) { + this.collisionMask = bitmask; + }, + + /** + * the built-in function to solve the collision response + * @protected + * @name respondToCollision + * @memberOf me.Body + * @function + * @param {me.collision.ResponseObject} response the collision response object + */ + respondToCollision: function (response) { + // the overlap vector + var overlap = response.overlapV; + + // FIXME: Respond proportionally to object mass + + // Move out of the other object shape + this.entity.pos.sub(overlap); + + // adjust velocity + if (overlap.x !== 0) { + this.vel.x = ~~(0.5 + this.vel.x - overlap.x) || 0; + } + if (overlap.y !== 0) { + this.vel.y = ~~(0.5 + this.vel.y - overlap.y) || 0; + + // cancel the falling an jumping flags if necessary + this.falling = overlap.y >= 1; + this.jumping = overlap.y <= -1; + } + }, + + /** + * update the body bounding rect (private) + * the body rect size is here used to cache the total bounding rect + * @private + * @name updateBounds + * @memberOf me.Body + * @function + */ + updateBounds : function () { + if (this.shapes.length > 0) { + // reset the rect with default values + var _bounds = this.shapes[0].getBounds(); + this.pos.setV(_bounds.pos); + this.resize(_bounds.width, _bounds.height); + + for (var i = 1; i < this.shapes.length; i++) { + this.union(this.shapes[i].getBounds()); + } + } + + // update the parent entity bounds + this.entity.onBodyUpdate(this.pos, this.width, this.height); + + return this; + }, + + /** + * set the entity default velocity
+ * note : velocity is by default limited to the same value, see + * setMaxVelocity if needed
+ * @name setVelocity + * @memberOf me.Body + * @function + * @param {Number} x velocity on x axis + * @param {Number} y velocity on y axis + * @protected + */ + setVelocity : function (x, y) { + this.accel.x = x !== 0 ? x : this.accel.x; + this.accel.y = y !== 0 ? y : this.accel.y; + + // limit by default to the same max value + this.setMaxVelocity(x, y); + }, + + /** + * cap the entity velocity to the specified value
+ * @name setMaxVelocity + * @memberOf me.Body + * @function + * @param {Number} x max velocity on x axis + * @param {Number} y max velocity on y axis + * @protected + */ + setMaxVelocity : function (x, y) { + this.maxVel.x = x; + this.maxVel.y = y; + }, + + /** + * set the entity default friction
+ * @name setFriction + * @memberOf me.Body + * @function + * @param {Number} x horizontal friction + * @param {Number} y vertical friction + * @protected + */ + setFriction : function (x, y) { + this.friction.x = x || 0; + this.friction.y = y || 0; + }, + + /** + * apply friction to a vector + * @ignore + */ + applyFriction : function (vel) { + var fx = this.friction.x * me.timer.tick, + nx = vel.x + fx, + x = vel.x - fx, + fy = this.friction.y * me.timer.tick, + ny = vel.y + fy, + y = vel.y - fy; + + vel.x = ( + (nx < 0) ? nx : + ( x > 0) ? x : 0 + ); + vel.y = ( + (ny < 0) ? ny : + ( y > 0) ? y : 0 + ); + }, + + /** + * compute the new velocity value + * @ignore + */ + computeVelocity : function (vel) { + + // apply gravity (if any) + if (this.gravity) { + // apply a constant gravity (if not on a ladder) + vel.y += this.gravity * me.timer.tick; + + // check if falling / jumping + this.falling = (vel.y > 0); + this.jumping = (this.falling ? false : this.jumping); + } + + // apply friction + if (this.friction.x || this.friction.y) { + this.applyFriction(vel); + } + + // cap velocity + if (vel.y !== 0) { + vel.y = vel.y.clamp(-this.maxVel.y, this.maxVel.y); + } + if (vel.x !== 0) { + vel.x = vel.x.clamp(-this.maxVel.x, this.maxVel.x); + } + }, + + /** + * update the body position + * @name update + * @memberOf me.Body + * @function + * @return {boolean} true if resulting velocity is different than 0 + */ + update : function (/* dt */) { + // update the velocity + this.computeVelocity(this.vel); + + // update player entity position + this.entity.pos.add(this.vel); + + // returns true if vel is different from 0 + return (this.vel.x !== 0 || this.vel.y !== 0); + }, + + /** + * Destroy function
+ * @ignore + */ + destroy : function () { + this.entity = null; + this.shapes = []; + } + }); + + /** + * Base class for Body exception handling. + * @name Error + * @class + * @memberOf me.Body + * @constructor + * @param {String} msg Error message. + */ + me.Body.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Body.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm. + * Based on the QuadTree Library by Timo Hausmann and released under the MIT license + * https://github.com/timohausmann/quadtree-js/ +**/ + +(function () { + + + /** + * a pool of `QuadTree` objects + */ + var QT_ARRAY = []; + + /** + * will pop a quadtree object from the array + * or create a new one if the array is empty + */ + var QT_ARRAY_POP = function (bounds, max_objects, max_levels, level) { + if (QT_ARRAY.length > 0) { + var _qt = QT_ARRAY.pop(); + _qt.bounds = bounds; + _qt.max_objects = max_objects || 4; + _qt.max_levels = max_levels || 4; + _qt.level = level || 0; + return _qt; + } else { + return new me.QuadTree(bounds, max_objects, max_levels, level); + } + }; + + /** + * Push back a quadtree back into the array + */ + var QT_ARRAY_PUSH = function (qt) { + QT_ARRAY.push(qt); + }; + + /** + * a temporary vector object to be reused + */ + var QT_VECTOR = new me.Vector2d(); + + + /** + * Quadtree Constructor
+ * note: the global quadtree instance is available through `me.collision.quadTree` + * @class + * @name QuadTree + * @extends Object + * @memberOf me + * @constructor + * @see me.collision.quadTree + * @param {me.Rect} bounds bounds of the node + * @param {Number} [max_objects=4] max objects a node can hold before splitting into 4 subnodes + * @param {Number} [max_levels=4] total max levels inside root Quadtree + * @param {Number} [level] deepth level, required for subnodes + */ + function Quadtree(bounds, max_objects, max_levels, level) { + this.max_objects = max_objects || 4; + this.max_levels = max_levels || 4; + + this.level = level || 0; + this.bounds = bounds; + + this.objects = []; + this.nodes = []; + } + + + /* + * Split the node into 4 subnodes + */ + Quadtree.prototype.split = function () { + + var nextLevel = this.level + 1, + subWidth = ~~(0.5 + this.bounds.width / 2), + subHeight = ~~(0.5 + this.bounds.height / 2), + x = ~~(0.5 + this.bounds.pos.x), + y = ~~(0.5 + this.bounds.pos.y); + + //top right node + this.nodes[0] = QT_ARRAY_POP({ + pos : { + x : x + subWidth, + y : y + }, + width : subWidth, + height : subHeight + }, this.max_objects, this.max_levels, nextLevel); + + //top left node + this.nodes[1] = QT_ARRAY_POP({ + pos : { + x : x, + y : y + }, + width : subWidth, + height : subHeight + }, this.max_objects, this.max_levels, nextLevel); + + //bottom left node + this.nodes[2] = QT_ARRAY_POP({ + pos : { + x : x, + y : y + subHeight + }, + width : subWidth, + height : subHeight + }, this.max_objects, this.max_levels, nextLevel); + + //bottom right node + this.nodes[3] = QT_ARRAY_POP({ + pos : { + x : x + subWidth, + y : y + subHeight + }, + width : subWidth, + height : subHeight + }, this.max_objects, this.max_levels, nextLevel); + }; + + + /* + * Determine which node the object belongs to + * @param {me.Rect} rect bounds of the area to be checked + * @return Integer index of the subnode (0-3), or -1 if rect cannot completely fit within a subnode and is part of the parent node + */ + Quadtree.prototype.getIndex = function (item) { + + var rect = item.getBounds(), + pos = rect.pos; + + // use world coordinates for floating items + if (item.floating || (item.ancestor && item.ancestor.floating)) { + pos = me.game.viewport.localToWorld(pos.x, pos.y, QT_VECTOR); + } + + var index = -1, + rx = pos.x, + ry = pos.y, + rw = rect.width, + rh = rect.height, + verticalMidpoint = this.bounds.pos.x + (this.bounds.width / 2), + horizontalMidpoint = this.bounds.pos.y + (this.bounds.height / 2), + //rect can completely fit within the top quadrants + topQuadrant = (ry < horizontalMidpoint && ry + rh < horizontalMidpoint), + //rect can completely fit within the bottom quadrants + bottomQuadrant = (ry > horizontalMidpoint); + + //rect can completely fit within the left quadrants + if (rx < verticalMidpoint && rx + rw < verticalMidpoint) { + if (topQuadrant) { + index = 1; + } else if (bottomQuadrant) { + index = 2; + } + } else if (rx > verticalMidpoint) { + //rect can completely fit within the right quadrants + if (topQuadrant) { + index = 0; + } else if (bottomQuadrant) { + index = 3; + } + } + + return index; + }; + + /** + * Insert the given object container into the node. + * @name insertContainer + * @memberOf me.QuadTree + * @function + * @param {me.Container} container group of objects to be added + */ + Quadtree.prototype.insertContainer = function (container) { + + for (var i = container.children.length, child; i--, (child = container.children[i]);) { + if (child instanceof me.Container && !(child instanceof me.ParticleContainer)) { + if (child.name !== "rootContainer") { + this.insert(child); + } + // recursivly insert all childs + this.insertContainer(child); + } else { + // only insert object with a bounding box + if (typeof (child.getBounds) === "function") { + this.insert(child); + } + } + } + }; + + /** + * Insert the given object into the node. If the node + * exceeds the capacity, it will split and add all + * objects to their corresponding subnodes. + * @name insert + * @memberOf me.QuadTree + * @function + * @param {Object} item object to be added + */ + Quadtree.prototype.insert = function (item) { + + var index = -1; + + //if we have subnodes ... + if (this.nodes.length > 0) { + index = this.getIndex(item); + + if (index !== -1) { + this.nodes[index].insert(item); + return; + } + } + + this.objects.push(item); + + if (this.objects.length > this.max_objects && this.level < this.max_levels) { + + //split if we don't already have subnodes + if (this.nodes.length === 0) { + this.split(); + } + + var i = 0; + + //add all objects to there corresponding subnodes + while (i < this.objects.length) { + + index = this.getIndex(this.objects[i]); + + if (index !== -1) { + this.nodes[index].insert(this.objects.splice(i, 1)[0]); + } else { + i = i + 1; + } + } + } + }; + + /** + * Return all objects that could collide with the given object + * @name retrieve + * @memberOf me.QuadTree + * @function + * @param {Object} object object to be checked against + * @param {Object} [function] a sorting function for the returned array + * @return {Object[]} array with all detected objects + */ + Quadtree.prototype.retrieve = function (item, fn) { + + var returnObjects = this.objects; + + //if we have subnodes ... + if (this.nodes.length > 0) { + + var index = this.getIndex(item); + + //if rect fits into a subnode .. + if (index !== -1) { + returnObjects = returnObjects.concat(this.nodes[index].retrieve(item)); + } else { + //if rect does not fit into a subnode, check it against all subnodes + for (var i = 0; i < this.nodes.length; i = i + 1) { + returnObjects = returnObjects.concat(this.nodes[i].retrieve(item)); + } + } + } + + if (typeof(fn) === "function") { + returnObjects.sort(fn); + } + + return returnObjects; + }; + + /** + * Remove the given item from the quadtree. + * (this function won't recalculate the impacted node) + * @name remove + * @memberOf me.QuadTree + * @function + * @param {Object} object object to be removed + * @return true if the item was found and removed. + */ + Quadtree.prototype.remove = function (item) { + var found = false; + + if (typeof (item.getBounds) === "undefined") { + // ignore object that cannot be added in the first place + return false; + } + + //if we have subnodes ... + if (this.nodes.length > 0) { + // determine to which node the item belongs to + var index = this.getIndex(item); + + if (index !== -1) { + found = this.nodes[index].remove(item); + // trim node if empty + if (found && this.nodes[index].isPrunable()) { + this.nodes.splice(index, 1); + } + } + } + + if (found === false) { + // try and remove the item from the list of items in this node + if (this.objects.indexOf(item) !== -1) { + this.objects.remove(item); + found = true; + } + } + + return found; + }; + + /** + * return true if the node is prunable + * @name isPrunable + * @memberOf me.QuadTree + * @function + * @return true if the node is prunable + */ + Quadtree.prototype.isPrunable = function () { + return !(this.hasChildren() || (this.objects.length > 0)); + }; + + /** + * return true if the node has any children + * @name hasChildren + * @memberOf me.QuadTree + * @function + * @return true if the node has any children + */ + Quadtree.prototype.hasChildren = function () { + for (var i = 0; i < this.nodes.length; i = i + 1) { + var subnode = this.nodes[i]; + if (subnode.length > 0 || subnode.objects.length > 0) { + return true; + } + } + return false; + }; + + /** + * clear the quadtree + * @name clear + * @memberOf me.QuadTree + * @function + */ + Quadtree.prototype.clear = function (bounds) { + + this.objects.length = 0; + + for (var i = 0; i < this.nodes.length; i = i + 1) { + this.nodes[i].clear(); + // recycle the quadTree object + QT_ARRAY_PUSH(this.nodes[i]); + } + // empty the array + this.nodes = []; + + // resize the root bounds if required + if (typeof bounds !== "undefined") { + this.bounds.setShape(bounds.pos.x, bounds.pos.y, bounds.width, bounds.height); + } + }; + + //make Quadtree available in the me namespace + me.QuadTree = Quadtree; + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Separating Axis Theorem implementation, based on the SAT.js library by Jim Riecken + * Available under the MIT License - https://github.com/jriecken/sat-js + */ + +(function () { + + /** + * Constants for Vornoi regions + * @ignore + */ + var LEFT_VORNOI_REGION = -1; + + /** + * Constants for Vornoi regions + * @ignore + */ + var MIDDLE_VORNOI_REGION = 0; + + /** + * Constants for Vornoi regions + * @ignore + */ + var RIGHT_VORNOI_REGION = 1; + + + /** + * A pool of `Vector` objects that are used in calculations to avoid allocating memory. + * @type {Array.} + */ + var T_VECTORS = []; + for (var v = 0; v < 10; v++) { T_VECTORS.push(new me.Vector2d()); } + + /** + * A pool of arrays of numbers used in calculations to avoid allocating memory. + * @type {Array.>} + */ + var T_ARRAYS = []; + for (var a = 0; a < 5; a++) { T_ARRAYS.push([]); } + + + /** + * Flattens the specified array of points onto a unit vector axis, + * resulting in a one dimensional range of the minimum and + * maximum value on that axis. + * @param {Array.} points The points to flatten. + * @param {Vector} normal The unit vector axis to flatten on. + * @param {Array.} result An array. After calling this function, + * result[0] will be the minimum value, + * result[1] will be the maximum value. + */ + function flattenPointsOn(points, normal, result) { + var min = Number.MAX_VALUE; + var max = -Number.MAX_VALUE; + var len = points.length; + for (var i = 0; i < len; i++) { + // The magnitude of the projection of the point onto the normal + var dot = points[i].dotProduct(normal); + if (dot < min) { min = dot; } + if (dot > max) { max = dot; } + } + result[0] = min; + result[1] = max; + } + + /** + * Check whether two convex polygons are separated by the specified + * axis (must be a unit vector). + * @param {Vector} aPos The position of the first polygon. + * @param {Vector} bPos The position of the second polygon. + * @param {Array.} aPoints The points in the first polygon. + * @param {Array.} bPoints The points in the second polygon. + * @param {Vector} axis The axis (unit sized) to test against. The points of both polygons + * will be projected onto this axis. + * @param {Response=} response A Response object (optional) which will be populated + * if the axis is not a separating axis. + * @return {boolean} true if it is a separating axis, false otherwise. If false, + * and a response is passed in, information about how much overlap and + * the direction of the overlap will be populated. + */ + function isSeparatingAxis(aPos, bPos, aPoints, bPoints, axis, response) { + var rangeA = T_ARRAYS.pop(); + var rangeB = T_ARRAYS.pop(); + // The magnitude of the offset between the two polygons + var offsetV = T_VECTORS.pop().copy(bPos).sub(aPos); + var projectedOffset = offsetV.dotProduct(axis); + + // Project the polygons onto the axis. + flattenPointsOn(aPoints, axis, rangeA); + flattenPointsOn(bPoints, axis, rangeB); + // Move B's range to its position relative to A. + rangeB[0] += projectedOffset; + rangeB[1] += projectedOffset; + // Check if there is a gap. If there is, this is a separating axis and we can stop + if (rangeA[0] > rangeB[1] || rangeB[0] > rangeA[1]) { + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return true; + } + + // This is not a separating axis. If we're calculating a response, calculate the overlap. + if (response) { + var overlap = 0; + // A starts further left than B + if (rangeA[0] < rangeB[0]) { + response.aInB = false; + // A ends before B does. We have to pull A out of B + if (rangeA[1] < rangeB[1]) { + overlap = rangeA[1] - rangeB[0]; + response.bInA = false; + // B is fully inside A. Pick the shortest way out. + } else { + var option1 = rangeA[1] - rangeB[0]; + var option2 = rangeB[1] - rangeA[0]; + overlap = option1 < option2 ? option1 : -option2; + } + // B starts further left than A + } else { + response.bInA = false; + // B ends before A ends. We have to push A out of B + if (rangeA[1] > rangeB[1]) { + overlap = rangeA[0] - rangeB[1]; + response.aInB = false; + // A is fully inside B. Pick the shortest way out. + } else { + var option11 = rangeA[1] - rangeB[0]; + var option22 = rangeB[1] - rangeA[0]; + overlap = option11 < option22 ? option11 : -option22; + } + } + + // If this is the smallest amount of overlap we've seen so far, set it as the minimum overlap. + var absOverlap = Math.abs(overlap); + if (absOverlap < response.overlap) { + response.overlap = absOverlap; + response.overlapN.copy(axis); + if (overlap < 0) { + response.overlapN.negateSelf(); + } + } + } + T_VECTORS.push(offsetV); + T_ARRAYS.push(rangeA); + T_ARRAYS.push(rangeB); + return false; + } + + + /** + * Calculates which Vornoi region a point is on a line segment.
+ * It is assumed that both the line and the point are relative to `(0,0)`
+ *
+     *             |       (0)      |
+     *      (-1)  [S]--------------[E]  (1)
+     *             |       (0)      |
+     * 
+ * + * @ignore + * @param {Vector} line The line segment. + * @param {Vector} point The point. + * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, + * MIDDLE_VORNOI_REGION (0) if it is the middle region, + * RIGHT_VORNOI_REGION (1) if it is the right region. + */ + function vornoiRegion(line, point) { + var len2 = line.length2(); + var dp = point.dotProduct(line); + if (dp < 0) { + // If the point is beyond the start of the line, it is in the + // left vornoi region. + return LEFT_VORNOI_REGION; + } else if (dp > len2) { + // If the point is beyond the end of the line, it is in the + // right vornoi region. + return RIGHT_VORNOI_REGION; + } else { + // Otherwise, it's in the middle one. + return MIDDLE_VORNOI_REGION; + } + } + + /** + * A singleton for managing collision detection (and projection-based collision response) of 2D shapes.
+ * Based on the Separating Axis Theorem and supports detecting collisions between simple Axis-Aligned Boxes, convex polygons and circles based shapes. + * @namespace me.collision + * @memberOf me + */ + me.collision = (function () { + // hold public stuff in our singleton + var api = {}; + + /* + * PUBLIC STUFF + */ + + /** + * the world quadtree used for the collision broadphase + * @name quadTree + * @memberOf me.collision + * @public + * @type {me.QuadTree} + */ + api.quadTree = null; + + /** + * The maximum number of levels that the quadtree will create. Default is 4. + * @name maxDepth + * @memberOf me.collision + * @public + * @type {number} + * @see me.collision.quadTree + * + */ + api.maxDepth = 4; + + /** + * The maximum number of children that a quadtree node can contain before it is split into sub-nodes. Default is 8. + * @name maxChildren + * @memberOf me.collision + * @public + * @type {boolean} + * @see me.collision.quadTree + */ + api.maxChildren = 8; + + /** + * bounds of the physic world. + * @name bounds + * @memberOf me.collision + * @public + * @type {me.Rect} + */ + api.bounds = null; + + /** + * Enum for collision type values. + * @property NO_OBJECT to disable collision check + * @property PLAYER_OBJECT + * @property NPC_OBJECT + * @property ENEMY_OBJECT + * @property COLLECTABLE_OBJECT + * @property ACTION_OBJECT e.g. doors + * @property PROJECTILE_OBJECT e.g. missiles + * @property WORLD_SHAPE e.g. walls; for map collision shapes + * @property USER user-defined collision types (see example) + * @property ALL_OBJECT all of the above (including user-defined types) + * @readonly + * @enum {Number} + * @name types + * @memberOf me.collision + * @see me.body.setCollisionMask + * @see me.body.collisionType + * @example + * // set the entity body collision type + * myEntity.body.collisionType = me.collision.types.PLAYER_OBJECT; + * + * // filter collision detection with collision shapes, enemies and collectables + * myEntity.body.setCollisionMask( + * me.collision.types.WORLD_SHAPE | + * me.collision.types.ENEMY_OBJECT | + * me.collision.types.COLLECTABLE_OBJECT + * ); + * + * // User-defined collision types are defined using BITWISE LEFT-SHIFT: + * game.collisionTypes = { + * LOCKED_DOOR : me.collision.types.USER << 0, + * OPEN_DOOR : me.collision.types.USER << 1, + * LOOT : me.collision.types.USER << 2, + * }; + * + * // Set collision type for a door entity + * myDoorEntity.body.collisionType = game.collisionTypes.LOCKED_DOOR; + * + * // Set collision mask for the player entity, so it collides with locked doors and loot + * myPlayerEntity.body.setCollisionMask( + * me.collision.types.ENEMY_OBJECT | + * me.collision.types.WORLD_SHAPE | + * game.collisionTypes.LOCKED_DOOR | + * game.collisionTypes.LOOT + * ); + */ + api.types = { + /** to disable collision check */ + NO_OBJECT : 0, + PLAYER_OBJECT : 1 << 0, + NPC_OBJECT : 1 << 1, + ENEMY_OBJECT : 1 << 2, + COLLECTABLE_OBJECT : 1 << 3, + ACTION_OBJECT : 1 << 4, // door, etc... + PROJECTILE_OBJECT : 1 << 5, // missiles, etc... + WORLD_SHAPE : 1 << 6, // walls, etc... + USER : 1 << 7, // user-defined types start here... + ALL_OBJECT : 0xFFFFFFFF // all objects + }; + + /** + * Initialize the collision/physic world + * @ignore + */ + api.init = function () { + // default bounds to the game viewport + api.bounds = me.game.viewport.clone(); + // initializa the quadtree + api.quadTree = new me.QuadTree(api.bounds, api.maxChildren, api.maxDepth); + + // reset the collision detection engine if a TMX level is loaded + me.event.subscribe(me.event.LEVEL_LOADED, function () { + // default bounds to game world + api.bounds = me.game.world.clone(); + // reset the quadtree + api.quadTree.clear(api.bounds); + }); + }; + + /** + * An object representing the result of an intersection. + * @property {me.Entity} a The first object participating in the intersection + * @property {me.Entity} b The second object participating in the intersection + * @property {Number} overlap Magnitude of the overlap on the shortest colliding axis + * @property {me.Vector2d} overlapV The overlap vector (i.e. `overlapN.scale(overlap, overlap)`). If this vector is subtracted from the position of a, a and b will no longer be colliding + * @property {me.Vector2d} overlapN The shortest colliding axis (unit-vector) + * @property {Boolean} aInB Whether the first object is entirely inside the second + * @property {Boolean} bInA Whether the second object is entirely inside the first + * @property {Number} indexShapeA The index of the colliding shape for the object a body + * @property {Number} indexShapeB The index of the colliding shape for the object b body + * @name ResponseObject + * @memberOf me.collision + * @public + * @type {Object} + * @see me.collision.check + */ + api.ResponseObject = function () { + this.a = null; + this.b = null; + this.overlapN = new me.Vector2d(); + this.overlapV = new me.Vector2d(); + this.aInB = true; + this.bInA = true; + this.indexShapeA = -1; + this.indexShapeB = -1; + this.overlap = Number.MAX_VALUE; + }; + + /** + * Set some values of the response back to their defaults.
+ * Call this between tests if you are going to reuse a single
+ * Response object for multiple intersection tests
+ * (recommended as it will avoid allocating extra memory)
+ * @name clear + * @memberOf me.collision.ResponseObject + * @public + * @function + */ + api.ResponseObject.prototype.clear = function () { + this.aInB = true; + this.bInA = true; + this.overlap = Number.MAX_VALUE; + this.indexShapeA = -1; + this.indexShapeB = -1; + return this; + }; + + /** + * a global instance of a response object used for collision detection
+ * this object will be reused amongst collision detection call if not user-defined response is specified + * @name response + * @memberOf me.collision + * @public + * @type {me.collision.ResponseObject} + */ + api.response = new api.ResponseObject(); + + /** + * a callback used to determine if two objects should collide (based on both respective objects collision mask and type).
+ * you can redefine this function if you need any specific rules over what should collide with what. + * @name shouldCollide + * @memberOf me.collision + * @public + * @function + * @param {me.Entity} a a reference to the object A. + * @param {me.Entity} b a reference to the object B. + * @return {Boolean} true if they should collide, false otherwise + */ + api.shouldCollide = function (a, b) { + return ( + a.body && b.body && + (a.body.collisionMask & b.body.collisionType) !== 0 && + (a.body.collisionType & b.body.collisionMask) !== 0 + ); + }; + + /** + * Checks if the specified entity collides with others entities + * @name check + * @memberOf me.collision + * @public + * @function + * @param {me.Entity} obj entity to be tested for collision + * @param {me.collision.ResponseObject} [respObj=me.collision.response] a user defined response object that will be populated if they intersect. + * @return {Boolean} in case of collision, false otherwise + * @example + * update : function (dt) { + * // ... + * + * // handle collisions against other shapes + * me.collision.check(this); + * + * // ... + * }, + * + * // colision handler + * onCollision : function (response) { + * if (response.b.body.collisionType === me.collision.types.ENEMY_OBJECT) { + * // makes the other entity solid, by substracting the overlap vector to the current position + * this.pos.sub(response.overlapV); + * this.hurt(); + * // not solid + * return false; + * } + * // Make the object solid + * return true; + * }, + */ + api.check = function (objA, responseObject) { + var collision = 0; + var response = responseObject || api.response; + + // retreive a list of potential colliding objects + var candidates = api.quadTree.retrieve(objA); + + for (var i = candidates.length, objB; i--, (objB = candidates[i]);) { + + // check if both objects "should" collide + if ((objB !== objA) && api.shouldCollide(objA, objB) && + // fast AABB check if both bounding boxes are overlaping + objA.getBounds().overlaps(objB.getBounds())) { + + // go trough all defined shapes in A + var aLen = objA.body.shapes.length; + var bLen = objB.body.shapes.length; + if (aLen === 0 || bLen === 0) { + continue; + } + + var indexA = 0; + do { + var shapeA = objA.body.getShape(indexA); + // go through all defined shapes in B + var indexB = 0; + do { + var shapeB = objB.body.getShape(indexB); + + // full SAT collision check + if (api["test" + shapeA.shapeType + shapeB.shapeType] + .call( + this, + objA, // a reference to the object A + shapeA, + objB, // a reference to the object B + shapeB, + // clear response object before reusing + response.clear()) === true + ) { + // we touched something ! + collision++; + + // set the shape index + response.indexShapeA = indexA; + response.indexShapeB = indexB; + + // execute the onCollision callback + if (objA.onCollision(response, objB) !== false) { + objA.body.respondToCollision.call(objA.body, response); + } + if (objB.onCollision(response, objA) !== false) { + objB.body.respondToCollision.call(objB.body, response); + } + } + indexB++; + } while (indexB < bLen); + indexA++; + } while (indexA < aLen); + } + } + // we could return the amount of objects we collided with ? + return collision > 0; + }; + + /** + * Checks whether polygons collide. + * @ignore + * @param {me.Entity} a a reference to the object A. + * @param {me.Polygon} polyA a reference to the object A Polygon to be tested + * @param {me.Entity} b a reference to the object B. + * @param {me.Polygon} polyB a reference to the object B Polygon to be tested + * @param {Response=} response Response object (optional) that will be populated if they intersect. + * @return {boolean} true if they intersect, false if they don't. + */ + api.testPolygonPolygon = function (a, polyA, b, polyB, response) { + // specific point for + var aPoints = polyA.points; + var aNormals = polyA.normals; + var aLen = aNormals.length; + var bPoints = polyB.points; + var bNormals = polyB.normals; + var bLen = bNormals.length; + // aboslute shape position + var posA = T_VECTORS.pop().copy(a.pos).add(a.ancestor._absPos).add(polyA.pos); + var posB = T_VECTORS.pop().copy(b.pos).add(b.ancestor._absPos).add(polyB.pos); + var i; + + // If any of the edge normals of A is a separating axis, no intersection. + for (i = 0; i < aLen; i++) { + if (isSeparatingAxis(posA, posB, aPoints, bPoints, aNormals[i], response)) { + T_VECTORS.push(posA); + T_VECTORS.push(posB); + return false; + } + } + + // If any of the edge normals of B is a separating axis, no intersection. + for (i = 0; i < bLen; i++) { + if (isSeparatingAxis(posA, posB, aPoints, bPoints, bNormals[i], response)) { + T_VECTORS.push(posA); + T_VECTORS.push(posB); + return false; + } + } + + // Since none of the edge normals of A or B are a separating axis, there is an intersection + // and we've already calculated the smallest overlap (in isSeparatingAxis). Calculate the + // final overlap vector. + if (response) { + response.a = a; + response.b = b; + response.overlapV.copy(response.overlapN).scale(response.overlap); + } + T_VECTORS.push(posA); + T_VECTORS.push(posB); + return true; + }; + + /** + * Check if two Ellipse collide. + * @ignore + * @param {me.Entity} a a reference to the object A. + * @param {me.Ellipse} ellipseA a reference to the object A Ellipse to be tested + * @param {me.Entity} b a reference to the object B. + * @param {me.Ellipse} ellipseB a reference to the object B Ellipse to be tested + * @param {Response=} response Response object (optional) that will be populated if + * the circles intersect. + * @return {boolean} true if the circles intersect, false if they don't. + */ + api.testEllipseEllipse = function (a, ellipseA, b, ellipseB, response) { + // Check if the distance between the centers of the two + // circles is greater than their combined radius. + var differenceV = T_VECTORS.pop().copy(b.pos).add(b.ancestor._absPos).add(ellipseB.pos) + .sub(a.pos).add(a.ancestor._absPos).sub(ellipseA.pos); + var radiusA = ellipseA.radius; + var radiusB = ellipseB.radius; + var totalRadius = radiusA + radiusB; + var totalRadiusSq = totalRadius * totalRadius; + var distanceSq = differenceV.length2(); + // If the distance is bigger than the combined radius, they don't intersect. + if (distanceSq > totalRadiusSq) { + T_VECTORS.push(differenceV); + return false; + } + // They intersect. If we're calculating a response, calculate the overlap. + if (response) { + var dist = Math.sqrt(distanceSq); + response.a = a; + response.b = b; + response.overlap = totalRadius - dist; + response.overlapN.copy(differenceV.normalize()); + response.overlapV.copy(differenceV).scale(response.overlap); + response.aInB = radiusA <= radiusB && dist <= radiusB - radiusA; + response.bInA = radiusB <= radiusA && dist <= radiusA - radiusB; + } + T_VECTORS.push(differenceV); + return true; + }; + + /** + * Check if a polygon and an ellipse collide. + * @ignore + * @param {me.Entity} a a reference to the object A. + * @param {me.Polygon} polyA a reference to the object A Polygon to be tested + * @param {me.Entity} b a reference to the object B. + * @param {me.Ellipse} ellipseB a reference to the object B Ellipse to be tested + * @param {Response=} response Response object (optional) that will be populated if they intersect. + * @return {boolean} true if they intersect, false if they don't. + */ + api.testPolygonEllipse = function (a, polyA, b, ellipseB, response) { + // Get the position of the circle relative to the polygon. + var circlePos = T_VECTORS.pop().copy(b.pos).add(b.ancestor._absPos).add(ellipseB.pos) + .sub(a.pos).add(a.ancestor._absPos).sub(polyA.pos); + var radius = ellipseB.radius; + var radius2 = radius * radius; + var points = polyA.points; + var edges = polyA.edges; + var len = edges.length; + var edge = T_VECTORS.pop(); + var normal = T_VECTORS.pop(); + var point = T_VECTORS.pop(); + var dist = 0; + + // For each edge in the polygon: + for (var i = 0; i < len; i++) { + var next = i === len - 1 ? 0 : i + 1; + var prev = i === 0 ? len - 1 : i - 1; + var overlap = 0; + var overlapN = null; + + // Get the edge. + edge.copy(edges[i]); + // Calculate the center of the circle relative to the starting point of the edge. + point.copy(circlePos).sub(points[i]); + + // If the distance between the center of the circle and the point + // is bigger than the radius, the polygon is definitely not fully in + // the circle. + if (response && point.length2() > radius2) { + response.aInB = false; + } + + // Calculate which Vornoi region the center of the circle is in. + var region = vornoiRegion(edge, point); + var inRegion = true; + // If it's the left region: + if (region === LEFT_VORNOI_REGION) { + var point2 = null; + if (len > 1) { + // We need to make sure we're in the RIGHT_VORNOI_REGION of the previous edge. + edge.copy(edges[prev]); + // Calculate the center of the circle relative the starting point of the previous edge + point2 = T_VECTORS.pop().copy(circlePos).sub(points[prev]); + region = vornoiRegion(edge, point2); + if (region !== RIGHT_VORNOI_REGION) { + inRegion = false; + } + } + + if (inRegion) { + // It's in the region we want. Check if the circle intersects the point. + dist = point.length(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(normal); + T_VECTORS.push(point); + if (point2) { + T_VECTORS.push(point2); + } + return false; + } else if (response) { + // It intersects, calculate the overlap. + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + + if (point2) { + T_VECTORS.push(point2); + } + // If it's the right region: + } else if (region === RIGHT_VORNOI_REGION) { + if (len > 1) { + // We need to make sure we're in the left region on the next edge + edge.copy(edges[next]); + // Calculate the center of the circle relative to the starting point of the next edge. + point.copy(circlePos).sub(points[next]); + region = vornoiRegion(edge, point); + if (region !== LEFT_VORNOI_REGION) { + inRegion = false; + } + } + + if (inRegion) { + // It's in the region we want. Check if the circle intersects the point. + dist = point.length(); + if (dist > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(normal); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + response.bInA = false; + overlapN = point.normalize(); + overlap = radius - dist; + } + } + // Otherwise, it's the middle region: + } else { + // Need to check if the circle is intersecting the edge, + // Get the normal. + normal.copy(polyA.normals[i]); + // Find the perpendicular distance between the center of the + // circle and the edge. + dist = point.dotProduct(normal); + var distAbs = Math.abs(dist); + // If the circle is on the outside of the edge, there is no intersection. + if ((len === 1 || dist > 0) && distAbs > radius) { + // No intersection + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(normal); + T_VECTORS.push(point); + return false; + } else if (response) { + // It intersects, calculate the overlap. + overlapN = normal; + overlap = radius - dist; + // If the center of the circle is on the outside of the edge, or part of the + // circle is on the outside, the circle is not fully inside the polygon. + if (dist >= 0 || overlap < 2 * radius) { + response.bInA = false; + } + } + } + + // If this is the smallest overlap we've seen, keep it. + // (overlapN may be null if the circle was in the wrong Vornoi region). + if (overlapN && response && Math.abs(overlap) < Math.abs(response.overlap)) { + response.overlap = overlap; + response.overlapN.copy(overlapN); + } + } + + // Calculate the final overlap vector - based on the smallest overlap. + if (response) { + response.a = a; + response.b = b; + response.overlapV.copy(response.overlapN).scale(response.overlap); + } + T_VECTORS.push(circlePos); + T_VECTORS.push(edge); + T_VECTORS.push(normal); + T_VECTORS.push(point); + return true; + }; + + /** + * Check if an ellipse and a polygon collide.
+ * **NOTE:** This is slightly less efficient than testPolygonEllipse as it just + * runs testPolygonEllipse and reverses the response at the end. + * @ignore + * @param {me.Entity} a a reference to the object A. + * @param {me.Ellipse} ellipseA a reference to the object A Ellipse to be tested + * @param {me.Entity} a a reference to the object B. + * @param {me.Polygon} polyB a reference to the object B Polygon to be tested + * @param {Response=} response Response object (optional) that will be populated if + * they intersect. + * @return {boolean} true if they intersect, false if they don't. + */ + api.testEllipsePolygon = function (a, ellipseA, b, polyB, response) { + // Test the polygon against the circle. + var result = api.testPolygonEllipse(b, polyB, a, ellipseA, response); + if (result && response) { + // Swap A and B in the response. + var resa = response.a; + var aInB = response.aInB; + response.overlapN.negateSelf(); + response.overlapV.negateSelf(); + response.a = response.b; + response.b = resa; + response.aInB = response.bInA; + response.bInA = aInB; + } + return result; + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * A base class for renderable objects. + * @class + * @extends me.Rect + * @memberOf me + * @constructor + * @param {Number} x position of the renderable object + * @param {Number} y position of the renderable object + * @param {Number} width object width + * @param {Number} height object height + */ + me.Renderable = me.Rect.extend( + /** @scope me.Renderable.prototype */ + { + /** + * @ignore + */ + init : function (x, y, width, height) { + /** + * to identify the object as a renderable object + * @ignore + */ + this.isRenderable = true; + + /** + * the renderable default transformation matrix + * @public + * @type me.Matrix2d + * @name currentTransform + * @memberOf me.Renderable + */ + if (typeof this.currentTransform !== "undefined") { + this.currentTransform.identity(); + } else { + this.currentTransform = me.pool.pull("me.Matrix2d"); + } + + /** + * (G)ame (U)nique (Id)entifier"
+ * a GUID will be allocated for any renderable object added
+ * to an object container (including the `me.game.world` container) + * @public + * @type String + * @name GUID + * @memberOf me.Renderable + */ + this.GUID = undefined; + + /** + * Whether the renderable object is visible and within the viewport
+ * @public + * @readonly + * @type Boolean + * @default false + * @name inViewport + * @memberOf me.Renderable + */ + this.inViewport = false; + + /** + * Whether the renderable object will always update, even when outside of the viewport
+ * @public + * @type Boolean + * @default false + * @name alwaysUpdate + * @memberOf me.Renderable + */ + this.alwaysUpdate = false; + + /** + * Whether to update this object when the game is paused. + * @public + * @type Boolean + * @default false + * @name updateWhenPaused + * @memberOf me.Renderable + */ + this.updateWhenPaused = false; + + /** + * make the renderable object persistent over level changes
+ * @public + * @type Boolean + * @default false + * @name isPersistent + * @memberOf me.Renderable + */ + this.isPersistent = false; + + /** + * Define if a renderable follows screen coordinates (floating)
+ * or the world coordinates (not floating)
+ * @public + * @type Boolean + * @default false + * @name floating + * @memberOf me.Renderable + */ + this.floating = false; + + /** + * Define the object anchoring point
+ * This is used when positioning, or scaling the object
+ * The anchor point is a value between 0.0 and 1.0 (1.0 being the maximum size of the object)
+ * (0, 0) means the top-left corner,
+ * (1, 1) means the bottom-right corner,
+ * @public + * @type me.Vector2d + * @default <0.5,0.5> + * @name anchorPoint + * @memberOf me.Renderable + */ + this.anchorPoint = new me.Vector2d(0.5, 0.5); + + /** + * [EXPERIMENTAL] when enabled, an object container will automatically + * apply any defined transformation before calling the child draw method. + * @public + * @type Boolean + * @default false + * @name autoTransform + * @memberOf me.Renderable + * @example + * // enable "automatic" transformation when the object is activated + * onActivateEvent: function () { + * // reset the transformation matrix + * this.renderable.currentTransform.identity(); + * // ensure the anchor point is the renderable center + * this.renderable.anchorPoint.set(0.5, 0.5); + * // enable auto transform + * this.renderable.autoTransform = true; + * .... + * }, + * // add a rotation effect when updating the entity + * update : function (dt) { + * .... + * this.renderable.currentTransform.rotate(0.025); + * .... + * return this._super(me.Entity, 'update', [dt]); + * }, + */ + this.autoTransform = false; + + /** + * Define the renderable opacity
+ * Set to zero if you do not wish an object to be drawn + * @see me.Renderable#setOpacity + * @see me.Renderable#getOpacity + * @public + * @type Number + * @default 1.0 + * @name me.Renderable#alpha + */ + this.alpha = 1.0; + + /** + * a reference to the Container object that contains this renderable, + * or undefined if it has not been added to one. + * @public + * @type me.Container + * @default undefined + * @name me.Renderable#ancestor + */ + this.ancestor = undefined; + + /** + * The bounding rectangle for this renderable + * @ignore + * @type {me.Rect} + * @name _bounds + * @memberOf me.Renderable + */ + if (this._bounds) { + this._bounds.setShape(x, y, width, height); + } + else { + this._bounds = new me.Rect(x, y, width, height); + } + + /** + * Absolute position in the game world + * @ignore + * @type {me.Vector2d} + * @name _absPos + * @memberOf me.Renderable + */ + if (this._absPos) { + this._absPos.set(x, y); + } + else { + this._absPos = new me.Vector2d(x, y); + } + + /** + * Position of the Renderable relative to its parent container + * @public + * @type {me.ObservableVector3d} + * @name pos + * @memberOf me.Renderable + */ + if (this.pos instanceof me.ObservableVector3d) { + this.pos.setMuted(x, y, 0).setCallback(this.updateBoundsPos.bind(this)); + } else { + this.pos = new me.ObservableVector3d(x, y, 0, { onUpdate: this.updateBoundsPos.bind(this) }); + } + + this._width = width; + this._height = height; + + this.shapeType = "Rectangle"; + + // ensure it's fully opaque by default + this.setOpacity(1.0); + }, + + /** + * returns the bounding box for this renderable + * @name getBounds + * @memberOf me.Renderable + * @function + * @return {me.Rect} bounding box Rectangle object + */ + getBounds : function () { + return this._bounds; + }, + + /** + * get the renderable alpha channel value
+ * @name getOpacity + * @memberOf me.Renderable + * @function + * @return {Number} current opacity value between 0 and 1 + */ + getOpacity : function () { + return this.alpha; + }, + + /** + * set the renderable alpha channel value
+ * @name setOpacity + * @memberOf me.Renderable + * @function + * @param {Number} alpha opacity value between 0.0 and 1.0 + */ + setOpacity : function (alpha) { + if (typeof (alpha) === "number") { + this.alpha = alpha.clamp(0.0, 1.0); + // Set to 1 if alpha is NaN + if (this.alpha !== this.alpha) { + this.alpha = 1.0; + } + } + }, + + /** + * multiply the renderable currentTransform with the given matrix + * @name transform + * @memberOf me.Renderable + * @see me.Renderable#currentTransform + * @function + * @param {me.Matrix2d} matrix the transformation matrix + * @return {me.Renderable} Reference to this object for method chaining + */ + transform : function (m) { + var bounds = this.getBounds(); + this.currentTransform.multiply(m); + bounds.setPoints(bounds.transform(m).points); + bounds.pos.setV(this.pos); + return this; + }, + + /** + * scale the renderable around his anchor point + * @name scale + * @memberOf me.Renderable + * @function + * @param {Number} x a number representing the abscissa of the scaling vector. + * @param {Number} [y=x] a number representing the ordinate of the scaling vector. + * @return {me.Renderable} Reference to this object for method chaining + */ + scale : function (x, y) { + var _x = x, + _y = typeof(y) === "undefined" ? _x : y; + + // set the scaleFlag + this.currentTransform.scale(_x, _y); + // resize the bounding box + this.getBounds().resize(this.width * _x, this.height * _y); + return this; + }, + + /** + * scale the renderable around his anchor point + * @name scaleV + * @memberOf me.Renderable + * @function + * @param {me.Vector2d} vector scaling vector + * @return {me.Renderable} Reference to this object for method chaining + */ + scaleV : function (v) { + this.scale(v.x, v.y); + return this; + }, + + /** + * update function + * called by the game manager on each game loop + * @name update + * @memberOf me.Renderable + * @function + * @protected + * @param {Number} dt time since the last update in milliseconds. + * @return false + **/ + update : function () { + return false; + }, + + /** + * update the renderable's bounding rect (private) + * @private + * @name updateBoundsPos + * @memberOf me.Renderable + * @function + */ + updateBoundsPos : function (newX, newY) { + var bounds = this.getBounds(); + bounds.pos.set(newX, newY, bounds.pos.z); + // XXX: This is called from the constructor, before it gets an ancestor + if (this.ancestor) { + bounds.pos.add(this.ancestor._absPos); + } + return bounds; + }, + + /** + * update the bounds + * @private + * @deprecated + * @name updateBounds + * @memberOf me.Entity + * @function + */ + updateBounds : function () { + console.warn("Deprecated: me.Renderable.updateBounds"); + return me.Rect.prototype.updateBounds.apply(this); + }, + + /** + * object draw + * called by the game manager on each game loop + * @name draw + * @memberOf me.Renderable + * @function + * @protected + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object + **/ + draw : function (/*renderer*/) { + // empty one ! + }, + + /** + * Destroy function
+ * @ignore + */ + destroy : function () { + me.pool.push(this.currentTransform); + this.currentTransform = undefined; + this.onDestroyEvent.apply(this, arguments); + }, + + /** + * OnDestroy Notification function
+ * Called by engine before deleting the object + * @name onDestroyEvent + * @memberOf me.Renderable + * @function + */ + onDestroyEvent : function () { + // to be extended ! + } + }); + + /** + * width of the Renderable bounding box + * @public + * @type {Number} + * @name width + * @memberOf me.Renderable + */ + Object.defineProperty(me.Renderable.prototype, "width", { + /** + * @ignore + */ + get : function () { + return this._width; + }, + /** + * @ignore + */ + set : function (value) { + this.getBounds().width = value; + this._width = value; + }, + configurable : true + }); + + /** + * height of the Renderable bounding box + * @public + * @type {Number} + * @name height + * @memberOf me.Renderable + */ + Object.defineProperty(me.Renderable.prototype, "height", { + /** + * @ignore + */ + get : function () { + return this._height; + }, + /** + * @ignore + */ + set : function (value) { + this.getBounds().height = value; + this._height = value; + }, + configurable : true + }); + + /** + * Base class for Renderable exception handling. + * @name Error + * @class + * @memberOf me.Renderable + * @constructor + * @param {String} msg Error message. + */ + me.Renderable.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Renderable.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * An object to display a fixed or animated sprite on screen. + * @class + * @extends me.Sprite + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the sprite object + * @param {Number} y the y coordinates of the sprite object + * @param {Object} settings Contains additional parameters for the animation sheet + * @param {me.video.renderer.Texture|Image|String} settings.image reference to a texture, spritesheet image or to a texture atlas + * @param {Number} [settings.framewidth] Width of a single frame within the spritesheet + * @param {Number} [settings.frameheight] Height of a single frame within the spritesheet + * @param {me.Vector2d} [settings.anchorPoint={x:0.5, y:0.5}] Anchor point to draw the frame at (defaults to the center of the frame). + * @example + * // create a standalone sprite, with anchor in the center + * var sprite = new me.Sprite(0, 0, { + * image : "PlayerTexture", + * framewidth : 64, + * frameheight : 64, + * anchorPoint : new me.Vector2d(0.5, 0.5) + * }); + */ + me.Sprite = me.Renderable.extend( + /** @scope .prototype */ + { + /** @ignore */ + init : function (x, y, settings) { + + /** + * pause and resume animation
+ * default value : false; + * @public + * @type Boolean + * @name me.Sprite#animationpause + */ + this.animationpause = false; + + /** + * animation cycling speed (delay between frame in ms)
+ * default value : 100ms; + * @public + * @type Number + * @name me.Sprite#animationspeed + */ + this.animationspeed = 100; + + /** + * global offset for the position to draw from on the source image. + * @public + * @type me.Vector2d + * @name offset + * @memberOf me.Sprite + */ + this.offset = new me.Vector2d(); + + // hold all defined animation + this.anim = {}; + + // a flag to reset animation + this.resetAnim = null; + + // current frame information + // (reusing current, any better/cleaner place?) + this.current = { + //current frame texture offset + offset : new me.Vector2d(), + // current frame size + width : 0, + height : 0, + // Source rotation angle for pre-rotating the source image + angle : 0 + }; + + // animation frame delta + this.dt = 0; + + // keep track of when we flip + this._flip = { + lastX : false, + lastY : false + }; + + if (typeof (settings.flipX) !== "undefined") { + this._flip.lastX(!!settings.flipX); + } + if (typeof (settings.flipY) !== "undefined") { + this._flip.lastY(!!settings.flipY); + } + + // flicker settings + this._flicker = { + isFlickering : false, + duration : 0, + callback : null, + state : false + }; + + // Used by the game engine to adjust visibility as the + // sprite moves in and out of the viewport + this.isSprite = true; + + // set the proper image/texture to use + if (settings.image instanceof me.CanvasRenderer.prototype.Texture) { + // use the texture from the texture Atlas + this.image = settings.image.getTexture(); + this.textureAtlas = settings.image; + // check for defined region + if (typeof (settings.region) !== "undefined") { + // use a texture atlas + var region = settings.image.getRegion(settings.region); + if (region) { + // set the sprite region within the texture + this.setRegion(region); + settings.framewidth = settings.framewidth || region.width; + settings.frameheight = settings.frameheight || region.height; + } else { + // throw an error + throw new me.Renderable.Error("Texture - region for " + settings.region + " not found"); + } + } + } else { + // standard image or spritesheet + this.image = me.utils.getImage(settings.image); + settings.framewidth = settings.framewidth || this.image.width; + settings.frameheight = settings.frameheight || this.image.height; + this.textureAtlas = me.video.renderer.cache.get(this.image, settings).getAtlas(); + } + // update the default "current" size + this.current.width = settings.framewidth; + this.current.height = settings.frameheight; + + // store/reset the current atlas information if specified + if (typeof(settings.atlas) !== "undefined") { + this.textureAtlas = settings.atlas; + this.atlasIndices = settings.atlasIndices; + } else { + this.atlasIndices = null; + } + + // call the super constructor + me.Renderable.prototype.init.apply(this, [ + x, y, + this.current.width, + this.current.height + ]); + + // set the default rotation angle is defined in the settings + // * WARNING: rotating sprites decreases performance with Canvas Renderer + if (typeof (settings.rotation) !== "undefined") { + this.currentTransform.rotate(settings.rotation); + } + + // update anchorPoint + if (settings.anchorPoint) { + this.anchorPoint.set(settings.anchorPoint.x, settings.anchorPoint.y); + } + + // for sprite, addAnimation will return !=0 + if (this.addAnimation("default", null) !== 0) { + // set as default + this.setCurrentAnimation("default"); + } + }, + + /** + * return the flickering state of the object + * @name isFlickering + * @memberOf me.Sprite + * @function + * @return {Boolean} + */ + isFlickering : function () { + return this._flicker.isFlickering; + }, + + /** + * make the object flicker + * @name flicker + * @memberOf me.Sprite + * @function + * @param {Number} duration expressed in milliseconds + * @param {Function} callback Function to call when flickering ends + * @example + * // make the object flicker for 1 second + * // and then remove it + * this.flicker(1000, function () { + * me.game.world.removeChild(this); + * }); + */ + flicker : function (duration, callback) { + this._flicker.duration = duration; + if (this._flicker.duration <= 0) { + this._flicker.isFlickering = false; + this._flicker.callback = null; + } + else if (!this._flicker.isFlickering) { + this._flicker.callback = callback; + this._flicker.isFlickering = true; + } + }, + + /** + * Flip object on horizontal axis + * @name flipX + * @memberOf me.Sprite + * @function + * @param {Boolean} flip enable/disable flip + */ + flipX : function (flip) { + if (flip !== this._flip.lastX) { + console.warn("Deprecated: me.Sprite.flipX"); + this._flip.lastX = flip; + // invert the scale.x value + this.currentTransform.scaleX(-1); + } + }, + + /** + * Flip object on vertical axis + * @name flipY + * @memberOf me.Sprite + * @function + * @param {Boolean} flip enable/disable flip + */ + flipY : function (flip) { + if (flip !== this._flip.lastY) { + console.warn("Deprecated: me.Sprite.flipY"); + this._flip.lastY = flip; + // invert the scale.x value + this.currentTransform.scaleY(-1); + } + }, + + /** + * add an animation
+ * For fixed-sized cell sprite sheet, the index list must follow the + * logic as per the following example :
+ * + * @name addAnimation + * @memberOf me.Sprite + * @function + * @param {String} name animation id + * @param {Number[]|String[]|Object[]} index list of sprite index or name + * defining the animation. Can also use objects to specify delay for each frame, see below + * @param {Number} [animationspeed] cycling speed for animation in ms + * @return {Number} frame amount of frame added to the animation + * (delay between each frame). + * @see me.Sprite#animationspeed + * @example + * // walking animation + * this.addAnimation("walk", [ 0, 1, 2, 3, 4, 5 ]); + * // eating animation + * this.addAnimation("eat", [ 6, 6 ]); + * // rolling animation + * this.addAnimation("roll", [ 7, 8, 9, 10 ]); + * // slower animation + * this.addAnimation("roll", [ 7, 8, 9, 10 ], 200); + * // or get more specific with delay for each frame. Good solution instead of repeating: + * this.addAnimation("turn", [{ name: 0, delay: 200 }, { name: 1, delay: 100 }]) + * // can do this with atlas values as well: + * this.addAnimation("turn", [{ name: "turnone", delay: 200 }, { name: "turntwo", delay: 100 }]) + * // define an dying animation that stop on the last frame + * this.addAnimation("die", [{ name: 3, delay: 200 }, { name: 4, delay: 100 }, { name: 5, delay: Infinity }]) + */ + addAnimation : function (name, index, animationspeed) { + this.anim[name] = { + name : name, + frames : [], + idx : 0, + length : 0 + }; + + // # of frames + var counter = 0; + + if (typeof (this.textureAtlas) !== "object") { + return 0; + } + + + if (index == null) { + index = []; + // create a default animation with all frame + Object.keys(this.textureAtlas).forEach(function (v, i) { + index[i] = i; + }); + } + + // set each frame configuration (offset, size, etc..) + for (var i = 0, len = index.length; i < len; i++) { + var frame = index[i]; + var frameObject; + if (typeof(frame) === "number" || typeof(frame) === "string") { + frameObject = { + name: frame, + delay: animationspeed || this.animationspeed + }; + } + else { + frameObject = frame; + } + var frameObjectName = frameObject.name; + if (typeof(frameObjectName) === "number") { + if (typeof (this.textureAtlas[frameObjectName]) !== "undefined") { + // TODO: adding the cache source coordinates add undefined entries in webGL mode + this.anim[name].frames[i] = Object.assign( + {}, + this.textureAtlas[frameObjectName], + frameObject + ); + counter++; + } + } else { // string + if (this.atlasIndices === null) { + throw new me.Renderable.Error( + "string parameters for addAnimation are not allowed for standard spritesheet based Texture" + ); + } else { + this.anim[name].frames[i] = Object.assign( + {}, + this.textureAtlas[this.atlasIndices[frameObjectName]], + frameObject + ); + counter++; + } + } + } + this.anim[name].length = counter; + + return counter; + }, + + /** + * set the current animation + * this will always change the animation & set the frame to zero + * @name setCurrentAnimation + * @memberOf me.Sprite + * @function + * @param {String} name animation id + * @param {String|Function} [onComplete] animation id to switch to when + * complete, or callback + * @example + * // set "walk" animation + * this.setCurrentAnimation("walk"); + * + * // set "walk" animation if it is not the current animation + * if (this.isCurrentAnimation("walk")) { + * this.setCurrentAnimation("walk"); + * } + * + * // set "eat" animation, and switch to "walk" when complete + * this.setCurrentAnimation("eat", "walk"); + * + * // set "die" animation, and remove the object when finished + * this.setCurrentAnimation("die", (function () { + * me.game.world.removeChild(this); + * return false; // do not reset to first frame + * }).bind(this)); + * + * // set "attack" animation, and pause for a short duration + * this.setCurrentAnimation("die", (function () { + * this.animationpause = true; + * + * // back to "standing" animation after 1 second + * setTimeout(function () { + * this.setCurrentAnimation("standing"); + * }, 1000); + * + * return false; // do not reset to first frame + * }).bind(this)); + **/ + setCurrentAnimation : function (name, resetAnim, _preserve_dt) { + if (this.anim[name]) { + this.current = this.anim[name]; + this.resetAnim = resetAnim || null; + this.setAnimationFrame(this.current.idx); + // XXX this should not be overwritten + this.current.name = name; + if (!_preserve_dt) { + this.dt = 0; + } + } else { + throw new me.Renderable.Error("animation id '" + name + "' not defined"); + } + }, + + /** + * return true if the specified animation is the current one. + * @name isCurrentAnimation + * @memberOf me.Sprite + * @function + * @param {String} name animation id + * @return {Boolean} + * @example + * if (!this.isCurrentAnimation("walk")) { + * // do something funny... + * } + */ + isCurrentAnimation : function (name) { + return this.current.name === name; + }, + + /** + * change the current texture atlas region for this sprite + * @see me.Texture.getRegion + * @name setRegion + * @memberOf me.Sprite + * @function + * @param {Object} region typically returned through me.Texture.getRegion() + * @example + * // change the sprite to "shadedDark13.png"; + * mySprite.setRegion(game.texture.getRegion("shadedDark13.png")); + */ + setRegion : function (region) { + // set the sprite offset within the texture + this.current.offset.setV(region.offset); + // set angle if defined + this.current.angle = region.angle; + // update the default "current" size + this.current.width = region.width; + this.current.height = region.height; + }, + + /** + * force the current animation frame index. + * @name setAnimationFrame + * @memberOf me.Sprite + * @function + * @param {Number} [index=0] animation frame index + * @example + * // reset the current animation to the first frame + * this.setAnimationFrame(); + */ + setAnimationFrame : function (idx) { + this.current.idx = (idx || 0) % this.current.length; + // XXX this should not be overwritten + var name = this.current.name; + var frame = this.getAnimationFrameObjectByIndex(this.current.idx); + // copy all properties of the current frame into current + Object.assign(this.current, frame); + // XXX this should not be overwritten + this.current.name = name; + // set global anchortPoint if defined + if (frame.anchorPoint) { + this.anchorPoint.setV(frame.anchorPoint); + } + }, + + /** + * return the current animation frame index. + * @name getCurrentAnimationFrame + * @memberOf me.Sprite + * @function + * @return {Number} current animation frame index + */ + getCurrentAnimationFrame : function () { + return this.current.idx; + }, + + /** + * Returns the frame object by the index. + * @name getAnimationFrameObjectByIndex + * @memberOf me.Sprite + * @function + * @private + * @return {Number} if using number indices. Returns {Object} containing frame data if using texture atlas + */ + getAnimationFrameObjectByIndex : function (id) { + return this.current.frames[id]; + }, + + /** + * update the animation
+ * this is automatically called by the game manager {@link me.game} + * @name update + * @memberOf me.Sprite + * @function + * @protected + * @param {Number} dt time since the last update in milliseconds. + */ + update : function (dt) { + var result = false; + // Update animation if necessary + if (!this.animationpause && this.current && this.current.length > 1) { + var duration = this.getAnimationFrameObjectByIndex(this.current.idx).delay; + this.dt += dt; + while (this.dt >= duration) { + result = true; + this.dt -= duration; + this.setAnimationFrame(this.current.idx + 1); + + // Switch animation if we reach the end of the strip and a callback is defined + if (this.current.idx === 0 && this.resetAnim) { + // If string, change to the corresponding animation + if (typeof this.resetAnim === "string") { + this.setCurrentAnimation(this.resetAnim, null, true); + } + // Otherwise is must be callable + else if (this.resetAnim() === false) { + // Reset to last frame + this.setAnimationFrame(this.current.length - 1); + + // Bail early without skipping any more frames. + this.dt %= duration; + break; + } + } + // Get next frame duration + duration = this.getAnimationFrameObjectByIndex(this.current.idx).delay; + } + } + + //update the "flickering" state if necessary + if (this._flicker.isFlickering) { + this._flicker.duration -= dt; + if (this._flicker.duration < 0) { + if (typeof (this._flicker.callback) === "function") { + this._flicker.callback(); + } + this.flicker(-1); + } + result = true; + } + + return result; + }, + + /** + * object draw
+ * not to be called by the end user
+ * called by the game manager on each game loop + * @name draw + * @memberOf me.Sprite + * @function + * @protected + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object + **/ + draw : function (renderer) { + // do nothing if we are flickering + if (this._flicker.isFlickering) { + this._flicker.state = !this._flicker.state; + if (!this._flicker.state) { + return; + } + } + + // the frame to draw + var frame = this.current; + + // cache the current position and size + var xpos = this.pos.x, + ypos = this.pos.y; + + var w = frame.width, + h = frame.height; + + // frame offset in the texture/atlas + var frame_offset = frame.offset; + var g_offset = this.offset; + + // save context + renderer.save(); + + // sprite alpha value + renderer.setGlobalAlpha(renderer.globalAlpha() * this.getOpacity()); + + // apply the renderable transformation matrix + if (!this.currentTransform.isIdentity()) { + renderer.transform(this.currentTransform); + } + + // translate to the defined anchor point + renderer.translate( + - ( w * this.anchorPoint.x ), + - ( h * this.anchorPoint.y ) + ); + + // remove image's TexturePacker/ShoeBox rotation + if (frame.angle !== 0) { + renderer.translate(-xpos, -ypos); + renderer.rotate(frame.angle); + xpos -= h; + w = frame.height; + h = frame.width; + } + + renderer.drawImage( + this.image, + g_offset.x + frame_offset.x, // sx + g_offset.y + frame_offset.y, // sy + w, h, // sw,sh + xpos, ypos, // dx,dy + w, h // dw,dh + ); + + // restore context + renderer.restore(); + } + }); + + // for backward compatiblity + me.AnimationSheet = me.Sprite; +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +(function () { + // some ref shortcut + var MIN = Math.min, MAX = Math.max; + + /** + * a camera/viewport Object + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Number} minX start x offset + * @param {Number} minY start y offset + * @param {Number} maxX end x offset + * @param {Number} maxY end y offset + */ + me.Viewport = me.Renderable.extend( + /** @scope me.Viewport.prototype */ { + /** @ignore */ + init : function (minX, minY, maxX, maxY) { + me.Renderable.prototype.init.apply(this, [minX, minY, maxX - minX, maxY - minY]); + + /** + * Axis definition + * @property NONE + * @property HORIZONTAL + * @property VERTICAL + * @property BOTH + * @public + * @constant + * @enum {Number} + * @name AXIS + * @memberOf me.Viewport + */ + this.AXIS = { + NONE : 0, + HORIZONTAL : 1, + VERTICAL : 2, + BOTH : 3 + }; + + /** + * Camera bounds + * @public + * @constant + * @type me.Rect + * @name bounds + * @memberOf me.Viewport + */ + this.bounds = new me.Rect(-Infinity, -Infinity, Infinity, Infinity); + + // offset for shake effect + this.offset = new me.Vector2d(); + + // target to follow + this.target = null; + + // default value follow + this.follow_axis = this.AXIS.NONE; + + // shake variables + this._shake = { + intensity : 0, + duration : 0, + axis : this.AXIS.BOTH, + onComplete : null + }; + + // flash variables + this._fadeOut = { + color : null, + tween : null + }; + // fade variables + this._fadeIn = { + color : null, + tween : null + }; + + // set a default deadzone + this.setDeadzone(this.width / 6, this.height / 6); + }, + + // -- some private function --- + + /** @ignore */ + _followH : function (target) { + var _x = this.pos.x; + if ((target.x - this.pos.x) > (this.deadzone.right)) { + this.pos.x = MIN((target.x) - (this.deadzone.right), this.bounds.width - this.width); + } + else if ((target.x - this.pos.x) < (this.deadzone.pos.x)) { + this.pos.x = MAX((target.x) - this.deadzone.pos.x, this.bounds.pos.x); + } + return (_x !== this.pos.x); + }, + + /** @ignore */ + _followV : function (target) { + var _y = this.pos.y; + if ((target.y - this.pos.y) > (this.deadzone.bottom)) { + this.pos.y = MIN((target.y) - (this.deadzone.bottom), this.bounds.height - this.height); + } + else if ((target.y - this.pos.y) < (this.deadzone.pos.y)) { + this.pos.y = MAX((target.y) - this.deadzone.pos.y, this.bounds.pos.y); + } + return (_y !== this.pos.y); + }, + + // -- public function --- + + /** + * reset the viewport to specified coordinates + * @name reset + * @memberOf me.Viewport + * @function + * @param {Number} [x=0] + * @param {Number} [y=0] + */ + reset : function (x, y) { + // reset the initial viewport position to 0,0 + this.pos.x = x || 0; + this.pos.y = y || 0; + + // reset the target + this.target = null; + + // reset default axis value for follow + this.follow_axis = null; + + // reset the transformation matrix + this.currentTransform.identity(); + }, + + /** + * change the deadzone settings. + * the "deadzone" defines an area within the current viewport in which + * the followed renderable can move without scrolling the viewport. + * @name setDeadzone + * @see me.Viewport.follow + * @memberOf me.Viewport + * @function + * @param {Number} w deadzone width + * @param {Number} h deadzone height + */ + setDeadzone : function (w, h) { + if (typeof(this.deadzone) === "undefined") { + this.deadzone = new me.Rect(0, 0, 0, 0); + } + + // reusing the old code for now... + this.deadzone.pos.set( + ~~((this.width - w) / 2), + ~~((this.height - h) / 2 - h * 0.25) + ); + this.deadzone.resize(w, h); + + // force a camera update + this.updateTarget(); + }, + + + /** + * resize the viewport + * @name resize + * @memberOf me.Viewport + * @function + * @param {Number} w new width of the viewport + * @param {Number} h new height of the viewport + * @return {me.Viewport} this viewport + */ + resize : function (w, h) { + me.Renderable.prototype.resize.apply(this, [w, h]); + var level = me.levelDirector.getCurrentLevel(); + + this.setBounds( + 0, 0, + Math.max(w, level ? level.width : 0), + Math.max(h, level ? level.height : 0) + ); + + this.setDeadzone(w / 6, h / 6); + this.moveTo(0, 0); + this.update(); + me.event.publish(me.event.VIEWPORT_ONRESIZE, [ this.width, this.height ]); + return this; + }, + + /** + * set the viewport boundaries (set to the world limit by default). + * the viewport is bound to the given coordinates and cannot move/be scrolled outside of it. + * @name setBounds + * @memberOf me.Viewport + * @function + * @param {Number} x world left limit + * @param {Number} y world top limit + * @param {Number} w world width limit + * @param {Number} h world height limit + */ + setBounds : function (x, y, w, h) { + this.bounds.pos.set(x, y); + this.bounds.resize(w, h); + this.moveTo(this.pos.x, this.pos.y); + }, + + /** + * set the viewport to follow the specified renderable.
+ * (this will put the viewport center around the given target) + * @name follow + * @memberOf me.Viewport + * @function + * @param {me.Renderable|me.Vector2d} target renderable or position + * Vector to follow + * @param {me.Viewport.AXIS} [axis=this.AXIS.BOTH] Which axis to follow + */ + follow : function (target, axis) { + if (target instanceof me.Renderable) { + this.target = target.pos; + } + else if ((target instanceof me.Vector2d) || (target instanceof me.Vector3d)) { + this.target = target; + } + else { + throw new me.Renderable.Error("invalid target for viewport.follow"); + } + // if axis is null, camera is moved on target center + this.follow_axis = ( + typeof(axis) === "undefined" ? this.AXIS.BOTH : axis + ); + // force a camera update + this.updateTarget(); + }, + + /** + * move the viewport upper-left position by the specified offset. + * @name move + * @memberOf me.Viewport + * @see me.Viewport.focusOn + * @function + * @param {Number} x + * @param {Number} y + * @example + * // Move the viewport up by four pixels + * me.game.viewport.move(0, -4); + */ + move : function (x, y) { + this.moveTo(this.pos.x + x, this.pos.y + y); + }, + + /** + * move the viewport upper-left position to the specified coordinates + * @name moveTo + * @memberOf me.Viewport + * @see me.Viewport.focusOn + * @function + * @param {Number} x + * @param {Number} y + */ + moveTo : function (x, y) { + this.pos.x = x.clamp( + this.bounds.pos.x, + this.bounds.width - this.width + ); + this.pos.y = y.clamp( + this.bounds.pos.y, + this.bounds.height - this.height + ); + + //publish the corresponding message + me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]); + }, + + /** @ignore */ + updateTarget : function () { + var updated = false; + + if (this.target) { + switch (this.follow_axis) { + case this.AXIS.NONE: + //this.focusOn(this.target); + break; + + case this.AXIS.HORIZONTAL: + updated = this._followH(this.target); + break; + + case this.AXIS.VERTICAL: + updated = this._followV(this.target); + break; + + case this.AXIS.BOTH: + updated = this._followH(this.target); + updated = this._followV(this.target) || updated; + break; + + default: + break; + } + } + + return updated; + }, + + /** @ignore */ + update : function (dt) { + var updated = this.updateTarget(); + + if (this._shake.duration > 0) { + this._shake.duration -= dt; + if (this._shake.duration <= 0) { + this._shake.duration = 0; + this.offset.setZero(); + if (typeof(this._shake.onComplete) === "function") { + this._shake.onComplete(); + } + } + else { + if (this._shake.axis === this.AXIS.BOTH || + this._shake.axis === this.AXIS.HORIZONTAL) { + this.offset.x = (Math.random() - 0.5) * this._shake.intensity; + } + if (this._shake.axis === this.AXIS.BOTH || + this._shake.axis === this.AXIS.VERTICAL) { + this.offset.y = (Math.random() - 0.5) * this._shake.intensity; + } + } + // updated! + updated = true; + } + + if (updated === true) { + //publish the corresponding message + me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]); + } + + // check for fade/flash effect + if ((this._fadeIn.tween != null) || (this._fadeOut.tween != null)) { + updated = true; + } + + return updated; + }, + + /** + * shake the camera + * @name shake + * @memberOf me.Viewport + * @function + * @param {Number} intensity maximum offset that the screen can be moved + * while shaking + * @param {Number} duration expressed in milliseconds + * @param {me.Viewport.AXIS} [axis=this.AXIS.BOTH] specify on which axis you + * want the shake effect + * @param {Function} [onComplete] callback once shaking effect is over + * @param {Boolean} [force] if true this will override the current effect + * @example + * // shake it baby ! + * me.game.viewport.shake(10, 500, me.game.viewport.AXIS.BOTH); + */ + shake : function (intensity, duration, axis, onComplete, force) { + if (this._shake.duration === 0 || force === true) { + this._shake.intensity = intensity; + this._shake.duration = duration; + this._shake.axis = axis || this.AXIS.BOTH; + this._shake.onComplete = typeof (onComplete) === "function" ? onComplete : undefined; + } + }, + + /** + * fadeOut(flash) effect

+ * screen is filled with the specified color and slowly goes back to normal + * @name fadeOut + * @memberOf me.Viewport + * @function + * @param {me.Color|String} color a CSS color value + * @param {Number} [duration=1000] expressed in milliseconds + * @param {Function} [onComplete] callback once effect is over + */ + fadeOut : function (color, duration, onComplete) { + this._fadeOut.color = me.pool.pull("me.Color").copy(color); + this._fadeOut.tween = me.pool.pull("me.Tween", this._fadeOut.color) + .to({ alpha: 0.0 }, duration || 1000) + .onComplete(onComplete || null); + this._fadeOut.tween.isPersistent = true; + this._fadeOut.tween.start(); + }, + + /** + * fadeIn effect

+ * fade to the specified color + * @name fadeIn + * @memberOf me.Viewport + * @function + * @param {me.Color|String} color a CSS color value + * @param {Number} [duration=1000] expressed in milliseconds + * @param {Function} [onComplete] callback once effect is over + */ + fadeIn : function (color, duration, onComplete) { + this._fadeIn.color = me.pool.pull("me.Color").copy(color); + var _alpha = this._fadeIn.color.alpha; + this._fadeIn.color.alpha = 0.0; + this._fadeIn.tween = me.pool.pull("me.Tween", this._fadeIn.color) + .to({ alpha: _alpha }, duration || 1000) + .onComplete(onComplete || null); + this._fadeIn.tween.isPersistent = true; + this._fadeIn.tween.start(); + }, + + /** + * return the viewport width + * @name getWidth + * @memberOf me.Viewport + * @function + * @return {Number} + */ + getWidth : function () { + return this.width; + }, + + /** + * return the viewport height + * @name getHeight + * @memberOf me.Viewport + * @function + * @return {Number} + */ + getHeight : function () { + return this.height; + }, + + /** + * set the viewport position around the specified object + * @name focusOn + * @memberOf me.Viewport + * @function + * @param {me.Renderable} + */ + focusOn : function (target) { + var bounds = target.getBounds(); + this.moveTo( + target.pos.x + bounds.pos.x + (bounds.width / 2), + target.pos.y + bounds.pos.y + (bounds.height / 2) + ); + }, + + /** + * check if the specified rectangle is in the viewport + * @name isVisible + * @memberOf me.Viewport + * @function + * @param {me.Rect} rect + * @return {Boolean} + */ + isVisible : function (rect) { + return rect.overlaps(this); + }, + + /** + * convert the given "local" (screen) coordinates into world coordinates + * @name localToWorld + * @memberOf me.Viewport + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} [v] an optional vector object where to set the + * converted value + * @return {me.Vector2d} + */ + localToWorld : function (x, y, v) { + v = v || new me.Vector2d(); + return (v.set(x, y)).add(this.pos).sub(me.game.world.pos); + }, + + /** + * convert the given world coordinates into "local" (screen) coordinates + * @name worldToLocal + * @memberOf me.Viewport + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} [v] an optional vector object where to set the + * converted value + * @return {me.Vector2d} + */ + worldToLocal : function (x, y, v) { + v = v || new me.Vector2d(); + return (v.set(x, y)).sub(this.pos).add(me.game.world.pos); + }, + + /** + * render the camera effects + * @ignore + */ + draw : function (renderer) { + // fading effect + if (this._fadeIn.tween) { + renderer.clearColor(this._fadeIn.color); + // remove the tween if over + if (this._fadeIn.color.alpha === 1.0) { + this._fadeIn.tween = null; + me.pool.push(this._fadeIn.color); + this._fadeIn.color = null; + } + } + + // flashing effect + if (this._fadeOut.tween) { + renderer.clearColor(this._fadeOut.color); + // remove the tween if over + if (this._fadeOut.color.alpha === 0.0) { + this._fadeOut.tween = null; + me.pool.push(this._fadeOut.color); + this._fadeOut.color = null; + } + } + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * GUI Object
+ * A very basic object to manage GUI elements
+ * The object simply register on the "pointerdown"
+ * or "touchstart" event and call the onClick function" + * @class + * @extends me.Sprite + * @memberOf me + * @constructor + * @param {Number} x the x coordinate of the GUI Object + * @param {Number} y the y coordinate of the GUI Object + * @param {Object} settings See {@link me.Entity} + * @example + * + * // create a basic GUI Object + * var myButton = me.GUI_Object.extend( + * { + * init:function (x, y) + * { + * var settings = {} + * settings.image = "button"; + * settings.framewidth = 100; + * settings.frameheight = 50; + * // super constructor + * me.GUI_Object.prototype.init.apply(this, [x, y, settings]); + * // define the object z order + * this.pos.z = 4; + * }, + * + * // output something in the console + * // when the object is clicked + * onClick:function (event) + * { + * console.log("clicked!"); + * // don't propagate the event + * return false; + * } + * }); + * + * // add the object at pos (10,10) + * me.game.world.addChild(new myButton(10,10)); + * + */ + me.GUI_Object = me.Sprite.extend({ + /** @scope me.GUI_Object.prototype */ + + /** + * @ignore + */ + init : function (x, y, settings) { + /** + * object can be clicked or not + * @public + * @type boolean + * @default true + * @name me.GUI_Object#isClickable + */ + this.isClickable = true; + + /** + * Tap and hold threshold timeout in ms + * @type {number} + * @default 250 + * @name me.GUI_Object#holdThreshold + */ + this.holdThreshold = 250; + + /** + * object can be tap and hold + * @public + * @type boolean + * @default false + * @name me.GUI_Object#isHoldable + */ + this.isHoldable = false; + + /** + * true if the pointer is over the object + * @public + * @type boolean + * @default false + * @name me.GUI_Object#hover + */ + this.hover = false; + + // object has been updated (clicked,etc..) + this.holdTimeout = null; + this.updated = false; + this.released = true; + + // call the parent constructor + me.Sprite.prototype.init.apply(this, [ x, y, settings ]); + + // GUI items use screen coordinates + this.floating = true; + }, + + /** + * return true if the object has been clicked + * @ignore + */ + update : function () { + if (this.updated) { + // clear the flag + if (!this.released) { + this.updated = false; + } + return true; + } + return false; + }, + + /** + * function callback for the pointerdown event + * @ignore + */ + clicked : function (event) { + // Check if left mouse button is pressed OR if device has touch + if ((event.which === 1 || me.device.touch) && this.isClickable) { + this.updated = true; + this.released = false; + if (this.isHoldable) { + if (this.holdTimeout !== null) { + me.timer.clearTimeout(this.holdTimeout); + } + this.holdTimeout = me.timer.setTimeout(this.hold.bind(this), this.holdThreshold, false); + this.released = false; + } + return this.onClick(event); + } + }, + + /** + * function called when the object is pressed
+ * to be extended
+ * return false if we need to stop propagating the event + * @name onClick + * @memberOf me.GUI_Object + * @public + * @function + * @param {Event} event the event object + */ + onClick : function (/* event */) { + return false; + }, + + /** + * function callback for the pointerEnter event + * @ignore + */ + enter : function (event) { + this.hover = true; + return this.onOver(event); + }, + + /** + * function called when the pointer is over the object + * @name onOver + * @memberOf me.GUI_Object + * @public + * @function + * @param {Event} event the event object + */ + onOver : function (/* event */) {}, + + /** + * function callback for the pointerLeave event + * @ignore + */ + leave : function (event) { + this.hover = false; + this.release.call(this, event); + return this.onOut(event); + }, + + /** + * function called when the pointer is leaving the object area + * @name onOut + * @memberOf me.GUI_Object + * @public + * @function + * @param {Event} event the event object + */ + onOut : function (/* event */) {}, + + /** + * function callback for the pointerup event + * @ignore + */ + release : function (event) { + if (this.released === false) { + this.released = true; + me.timer.clearTimeout(this.holdTimeout); + return this.onRelease(event); + } + }, + + /** + * function called when the object is pressed and released
+ * to be extended
+ * return false if we need to stop propagating the event + * @name onRelease + * @memberOf me.GUI_Object + * @public + * @function + * @param {Event} event the event object + */ + onRelease : function () { + return false; + }, + + /** + * function callback for the tap and hold timer event + * @ignore + */ + hold : function () { + me.timer.clearTimeout(this.holdTimeout); + if (!this.released) { + this.onHold(); + } + }, + + /** + * function called when the object is pressed and held
+ * to be extended
+ * @name onHold + * @memberOf me.GUI_Object + * @public + * @function + */ + onHold : function () {}, + + /** + * function called when added to the game world or a container + * @ignore + */ + onActivateEvent : function () { + // register pointer events + me.input.registerPointerEvent("pointerdown", this, this.clicked.bind(this)); + me.input.registerPointerEvent("pointerup", this, this.release.bind(this)); + me.input.registerPointerEvent("pointercancel", this, this.release.bind(this)); + me.input.registerPointerEvent("pointerenter", this, this.enter.bind(this)); + me.input.registerPointerEvent("pointerleave", this, this.leave.bind(this)); + }, + + /** + * function called when removed from the game world or a container + * @ignore + */ + onDeactivateEvent : function () { + // release pointer events + me.input.releasePointerEvent("pointerdown", this); + me.input.releasePointerEvent("pointerup", this); + me.input.releasePointerEvent("pointercancel", this); + me.input.releasePointerEvent("pointerenter", this); + me.input.releasePointerEvent("pointerleave", this); + me.timer.clearTimeout(this.holdTimeout); + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * Private function to re-use for object removal in a defer + * @ignore + */ + var deferredRemove = function (child, keepalive) { + this.removeChildNow(child, keepalive); + }; + + var globalFloatingCounter = 0; + + /** + * me.Container represents a collection of child objects + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Number} [x=0] position of the container + * @param {Number} [y=0] position of the container + * @param {Number} [w=me.game.viewport.width] width of the container + * @param {number} [h=me.game.viewport.height] height of the container + */ + me.Container = me.Renderable.extend( + /** @scope me.Container.prototype */ + { + /** + * constructor + * @ignore + */ + init : function (x, y, width, height) { + /** + * keep track of pending sort + * @ignore + */ + this.pendingSort = null; + + // TODO; container do not have a physic body + // ADD child container child one by one to the quadtree? + + /** + * whether the container is the root of the scene + * @private + * @ignore + */ + this._root = false; + + // call the _super constructor + me.Renderable.prototype.init.apply(this, + [x || 0, y || 0, + width || Infinity, + height || Infinity] + ); + + /** + * The array of children of this container. + * @ignore + */ + this.children = []; + + /** + * The property of the child object that should be used to sort on
+ * value : "x", "y", "z" + * @public + * @type String + * @default me.game.sortOn + * @name sortOn + * @memberOf me.Container + */ + this.sortOn = me.game.sortOn; + + /** + * Specify if the children list should be automatically sorted when adding a new child + * @public + * @type Boolean + * @default true + * @name autoSort + * @memberOf me.Container + */ + this.autoSort = true; + + /** + * Specify if the children z index should automatically be managed by the parent container + * @public + * @type Boolean + * @default true + * @name autoDepth + * @memberOf me.Container + */ + this.autoDepth = true; + + /** + * Used by the debug panel plugin + * @ignore + */ + this.drawCount = 0; + + /** + * The bounds that contains all its children + * @public + * @type me.Rect + * @name childBounds + * @memberOf me.Container + */ + this.childBounds = this.getBounds().clone(); + + // container self apply any defined transformation + this.autoTransform = false; + }, + + + /** + * Add a child to the container
+ * if auto-sort is disable, the object will be appended at the bottom of the list + * @name addChild + * @memberOf me.Container + * @function + * @param {me.Renderable} child + * @param {number} [z] forces the z index of the child to the specified value + * @return {me.Renderable} the added child + */ + addChild : function (child, z) { + if (child.ancestor instanceof me.Container) { + child.ancestor.removeChildNow(child); + } + else { + // only allocate a GUID if the object has no previous ancestor + // (e.g. move one child from one container to another) + if (child.isRenderable) { + // allocated a GUID value (use child.id as based index if defined) + child.GUID = me.utils.createGUID(child.id); + } + } + + child.ancestor = this; + this.children.push(child); + + // set the child z value if required + if (typeof(child.pos) !== "undefined") { + if (typeof(z) === "number") { + child.pos.z = z; + } else if (this.autoDepth === true) { + child.pos.z = this.children.length; + } + } + + if (this.autoSort === true) { + this.sort(); + } + + if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) { + child.onActivateEvent(); + } + + return child; + }, + + /** + * Add a child to the container at the specified index
+ * (the list won't be sorted after insertion) + * @name addChildAt + * @memberOf me.Container + * @function + * @param {me.Renderable} child + * @param {Number} index + * @return {me.Renderable} the added child + */ + addChildAt : function (child, index) { + if (index >= 0 && index < this.children.length) { + if (child.ancestor instanceof me.Container) { + child.ancestor.removeChildNow(child); + } + else { + // only allocate a GUID if the object has no previous ancestor + // (e.g. move one child from one container to another) + if (child.isRenderable) { + // allocated a GUID value + child.GUID = me.utils.createGUID(); + } + } + child.ancestor = this; + + this.children.splice(index, 0, child); + + if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) { + child.onActivateEvent(); + } + + return child; + } + else { + throw new me.Container.Error("Index (" + index + ") Out Of Bounds for addChildAt()"); + } + }, + + /** + * The forEach() method executes a provided function once per child element.
+ * callback is invoked with three arguments:
+ * - the element value
+ * - the element index
+ * - the array being traversed
+ * @name forEach + * @memberOf me.Container + * @function + * @param {Function} callback + * @param {Object} [thisArg] value to use as this(i.e reference Object) when executing callback. + * @example + * // iterate through all children of the root container + * me.game.world.forEach(function (child) { + * // do something with the child + * }); + */ + forEach : function (callback, thisArg) { + var context = this, i = 0; + + var len = this.children.length; + + if (typeof callback !== "function") { + throw new me.Container.Error(callback + " is not a function"); + } + + if (arguments.length > 1) { + context = thisArg; + } + + while (i < len) { + callback.call(context, this.children[i], i, this.children); + i++; + } + }, + + /** + * Swaps the position (z-index) of 2 children + * @name swapChildren + * @memberOf me.Container + * @function + * @param {me.Renderable} child + * @param {me.Renderable} child2 + */ + swapChildren : function (child, child2) { + var index = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if ((index !== -1) && (index2 !== -1)) { + // swap z index + var _z = child.pos.z; + child.pos.z = child2.pos.z; + child2.pos.z = _z; + // swap the positions.. + this.children[index] = child2; + this.children[index2] = child; + } + else { + throw new me.Container.Error(child + " Both the supplied childs must be a child of the caller " + this); + } + }, + + /** + * Returns the Child at the specified index + * @name getChildAt + * @memberOf me.Container + * @function + * @param {Number} index + */ + getChildAt : function (index) { + if (index >= 0 && index < this.children.length) { + return this.children[index]; + } + else { + throw new me.Container.Error("Index (" + index + ") Out Of Bounds for getChildAt()"); + } + }, + + /** + * Returns the index of the Child + * @name getChildAt + * @memberOf me.Container + * @function + * @param {me.Renderable} child + */ + getChildIndex : function (child) { + return this.children.indexOf(child); + }, + + /** + * Returns true if contains the specified Child + * @name hasChild + * @memberOf me.Container + * @function + * @param {me.Renderable} child + * @return {Boolean} + */ + hasChild : function (child) { + return this === child.ancestor; + }, + + /** + * return the child corresponding to the given property and value.
+ * note : avoid calling this function every frame since + * it parses the whole object tree each time + * @name getChildByProp + * @memberOf me.Container + * @public + * @function + * @param {String} prop Property name + * @param {String|RegExp|Number|Boolean} value Value of the property + * @return {me.Renderable[]} Array of childs + * @example + * // get the first child object called "mainPlayer" in a specific container : + * var ent = myContainer.getChildByProp("name", "mainPlayer"); + * + * // or query the whole world : + * var ent = me.game.world.getChildByProp("name", "mainPlayer"); + * + * // partial property matches are also allowed by using a RegExp. + * // the following matches "redCOIN", "bluecoin", "bagOfCoins", etc : + * var allCoins = me.game.world.getChildByProp("name", /coin/i); + * + * // searching for numbers or other data types : + * var zIndex10 = me.game.world.getChildByProp("z", 10); + * var inViewport = me.game.world.getChildByProp("inViewport", true); + */ + getChildByProp : function (prop, value) { + var objList = []; + + function compare(obj, prop) { + var v = obj[prop]; + if (value instanceof RegExp && typeof(v) === "string") { + if (value.test(v)) { + objList.push(obj); + } + } + else if (v === value) { + objList.push(obj); + } + } + + for (var i = this.children.length - 1; i >= 0; i--) { + var obj = this.children[i]; + compare(obj, prop); + if (obj instanceof me.Container) { + objList = objList.concat(obj.getChildByProp(prop, value)); + } + } + return objList; + }, + + /** + * returns the list of childs with the specified class type + * @name getChildByType + * @memberOf me.Container + * @public + * @function + * @param {Object} class type + * @return {me.Renderable[]} Array of children + */ + getChildByType : function (_class) { + var objList = []; + + for (var i = this.children.length - 1; i >= 0; i--) { + var obj = this.children[i]; + if (obj instanceof _class) { + objList.push(obj); + } + if (obj instanceof me.Container) { + objList = objList.concat(obj.getChildByType(_class)); + } + } + return objList; + }, + + /** + * returns the list of childs with the specified name
+ * as defined in Tiled (Name field of the Object Properties)
+ * note : avoid calling this function every frame since + * it parses the whole object list each time + * @name getChildByName + * @memberOf me.Container + * @public + * @function + * @param {String|RegExp|Number|Boolean} name entity name + * @return {me.Renderable[]} Array of children + */ + getChildByName : function (name) { + return this.getChildByProp("name", name); + }, + + /** + * return the child corresponding to the specified GUID
+ * note : avoid calling this function every frame since + * it parses the whole object list each time + * @name getChildByGUID + * @memberOf me.Container + * @public + * @function + * @param {String|RegExp|Number|Boolean} GUID entity GUID + * @return {me.Renderable} corresponding child or null + */ + getChildByGUID : function (guid) { + var obj = this.getChildByProp("GUID", guid); + return (obj.length > 0) ? obj[0] : null; + }, + + /** + * resizes the child bounds rectangle, based on children bounds. + * @name updateChildBounds + * @memberOf me.Container + * @function + * @return {me.Rect} updated child bounds + */ + updateChildBounds : function () { + this.childBounds.pos.set(Infinity, Infinity); + this.childBounds.resize(-Infinity, -Infinity); + var childBounds; + for (var i = this.children.length, child; i--, (child = this.children[i]);) { + if (child.isRenderable) { + if (child instanceof me.Container) { + childBounds = child.childBounds; + } + else { + childBounds = child.getBounds(); + } + // TODO : returns an "empty" rect instead of null (e.g. EntityObject) + // TODO : getBounds should always return something anyway + if (childBounds !== null) { + this.childBounds.union(childBounds); + } + } + } + return this.childBounds; + }, + + /** + * Checks if this container is root or if ti's attached to the root container. + * @private + * @name isAttachedToRoot + * @memberOf me.Container + * @function + * @returns Boolean + */ + isAttachedToRoot : function () { + if (this._root) { + return true; + } else { + var ancestor = this.ancestor; + while (ancestor) { + if (ancestor._root === true) { + return true; + } + ancestor = ancestor.ancestor; + } + return false; + } + }, + + /** + * update the renderable's bounding rect (private) + * @private + * @name updateBoundsPos + * @memberOf me.Container + * @function + */ + updateBoundsPos : function (newX, newY) { + me.Renderable.prototype.updateBoundsPos.apply(this, [ newX, newY ]); + + // Update container's absolute position + this._absPos.set(newX, newY); + if (this.ancestor) { + this._absPos.add(this.ancestor._absPos); + } + + // Notify children that the parent's position has changed + for (var i = this.children.length, child; i--, (child = this.children[i]);) { + if (child.isRenderable) { + child.updateBoundsPos(child.pos.x, child.pos.y); + } + } + + return this.getBounds(); + }, + + /** + * @ignore + */ + onActivateEvent : function () { + for (var i = this.children.length, child; i--, (child = this.children[i]);) { + if (typeof child.onActivateEvent === "function") { + child.onActivateEvent(); + } + } + }, + + /** + * Invokes the removeChildNow in a defer, to ensure the child is removed safely after the update & draw stack has completed + * @name removeChild + * @memberOf me.Container + * @public + * @function + * @param {me.Renderable} child + * @param {Boolean} [keepalive=False] True to prevent calling child.destroy() + */ + removeChild : function (child, keepalive) { + if (this.hasChild(child)) { + deferredRemove.defer(this, child, keepalive); + } + else { + throw new me.Container.Error("Child is not mine."); + } + }, + + + /** + * Removes (and optionally destroys) a child from the container.
+ * (removal is immediate and unconditional)
+ * Never use keepalive=true with objects from {@link me.pool}. Doing so will create a memory leak. + * @name removeChildNow + * @memberOf me.Container + * @function + * @param {me.Renderable} child + * @param {Boolean} [keepalive=False] True to prevent calling child.destroy() + */ + removeChildNow : function (child, keepalive) { + if (this.hasChild(child) && (this.getChildIndex(child) >= 0)) { + if (typeof child.onDeactivateEvent === "function") { + child.onDeactivateEvent(); + } + + if (!keepalive) { + if (typeof (child.destroy) === "function") { + child.destroy(); + } + + me.pool.push(child); + } + + // Don't cache the child index; another element might have been removed + // by the child's `onDeactivateEvent` or `destroy` methods + var childIndex = this.getChildIndex(child); + if (childIndex >= 0) { + this.children.splice(childIndex, 1); + child.ancestor = undefined; + } + } + }, + + /** + * Automatically set the specified property of all childs to the given value + * @name setChildsProperty + * @memberOf me.Container + * @function + * @param {String} property property name + * @param {Object} value property value + * @param {Boolean} [recursive=false] recursively apply the value to child containers if true + */ + setChildsProperty : function (prop, val, recursive) { + for (var i = this.children.length; i >= 0; i--) { + var obj = this.children[i]; + if ((recursive === true) && (obj instanceof me.Container)) { + obj.setChildsProperty(prop, val, recursive); + } + obj[prop] = val; + } + }, + + /** + * Move the child in the group one step forward (z depth). + * @name moveUp + * @memberOf me.Container + * @function + * @param {me.Renderable} child + */ + moveUp : function (child) { + var childIndex = this.getChildIndex(child); + if (childIndex - 1 >= 0) { + // note : we use an inverted loop + this.swapChildren(child, this.getChildAt(childIndex - 1)); + } + }, + + /** + * Move the child in the group one step backward (z depth). + * @name moveDown + * @memberOf me.Container + * @function + * @param {me.Renderable} child + */ + moveDown : function (child) { + var childIndex = this.getChildIndex(child); + if (childIndex >= 0 && (childIndex + 1) < this.children.length) { + // note : we use an inverted loop + this.swapChildren(child, this.getChildAt(childIndex + 1)); + } + }, + + /** + * Move the specified child to the top(z depth). + * @name moveToTop + * @memberOf me.Container + * @function + * @param {me.Renderable} child + */ + moveToTop : function (child) { + var childIndex = this.getChildIndex(child); + if (childIndex > 0) { + // note : we use an inverted loop + this.children.splice(0, 0, this.children.splice(childIndex, 1)[0]); + // increment our child z value based on the previous child depth + child.pos.z = this.children[1].pos.z + 1; + } + }, + + /** + * Move the specified child the bottom (z depth). + * @name moveToBottom + * @memberOf me.Container + * @function + * @param {me.Renderable} child + */ + moveToBottom : function (child) { + var childIndex = this.getChildIndex(child); + if (childIndex >= 0 && childIndex < (this.children.length - 1)) { + // note : we use an inverted loop + this.children.splice((this.children.length - 1), 0, this.children.splice(childIndex, 1)[0]); + // increment our child z value based on the next child depth + child.pos.z = this.children[(this.children.length - 2)].pos.z - 1; + } + }, + + /** + * Manually trigger the sort of all the childs in the container

+ * @name sort + * @memberOf me.Container + * @public + * @function + * @param {Boolean} [recursive=false] recursively sort all containers if true + */ + sort : function (recursive) { + // do nothing if there is already a pending sort + if (!this.pendingSort) { + if (recursive === true) { + // trigger other child container sort function (if any) + for (var i = this.children.length, obj; i--, (obj = this.children[i]);) { + if (obj instanceof me.Container) { + // note : this will generate one defered sorting function + // for each existing containe + obj.sort(recursive); + } + } + } + /** @ignore */ + this.pendingSort = function (self) { + // sort everything in this container + self.children.sort(self["_sort" + self.sortOn.toUpperCase()]); + // clear the defer id + self.pendingSort = null; + // make sure we redraw everything + me.game.repaint(); + }.defer(this, this); + } + }, + + /** + * @ignore + */ + onDeactivateEvent : function () { + for (var i = this.children.length, child; i--, (child = this.children[i]);) { + if (typeof child.onDeactivateEvent === "function") { + child.onDeactivateEvent(); + } + } + }, + + /** + * Z Sorting function + * @ignore + */ + _sortZ : function (a, b) { + return (b.pos && a.pos) ? (b.pos.z - a.pos.z) : (a.pos ? -Infinity : Infinity); + }, + + /** + * Reverse Z Sorting function + * @ignore + */ + _sortReverseZ : function (a, b) { + return (a.pos && b.pos) ? (a.pos.z - b.pos.z) : (a.pos ? Infinity : -Infinity); + }, + + /** + * X Sorting function + * @ignore + */ + _sortX : function (a, b) { + if (!b.pos || !a.pos) { + return (a.pos ? -Infinity : Infinity); + } + var result = b.pos.z - a.pos.z; + return (result ? result : (b.pos.x - a.pos.x)); + }, + + /** + * Y Sorting function + * @ignore + */ + _sortY : function (a, b) { + if (!b.pos || !a.pos) { + return (a.pos ? -Infinity : Infinity); + } + var result = b.pos.z - a.pos.z; + return (result ? result : (b.pos.y - a.pos.y)); + }, + + /** + * Destroy function
+ * @ignore + */ + destroy : function () { + // cancel any sort operation + if (this.pendingSort) { + clearTimeout(this.pendingSort); + this.pendingSort = null; + } + + // delete all children + for (var i = this.children.length, obj; i >= 0; (obj = this.children[--i])) { + // don't remove it if a persistent object + if (obj && !obj.isPersistent) { + this.removeChildNow(obj); + } + } + + // reset the transformation matrix + this.currentTransform.identity(); + }, + + /** + * @ignore + */ + update : function (dt) { + me.Renderable.prototype.update.apply(this, [dt]); + var isDirty = false; + var isFloating = false; + var isPaused = me.state.isPaused(); + var viewport = me.game.viewport; + + // Update container's absolute position + this._absPos.setV(this.pos); + if (this.ancestor) { + this._absPos.add(this.ancestor._absPos); + } + + for (var i = this.children.length, obj; i--, (obj = this.children[i]);) { + if (isPaused && (!obj.updateWhenPaused)) { + // skip this object + continue; + } + + if (obj.isRenderable) { + isFloating = (globalFloatingCounter > 0 || obj.floating); + if (isFloating) { + globalFloatingCounter++; + } + // check if object is visible + obj.inViewport = isFloating || viewport.isVisible(obj.getBounds()); + + // update our object + isDirty = ((obj.inViewport || obj.alwaysUpdate) && obj.update(dt)) || isDirty; + + // Update child's absolute position + obj._absPos.setV(this._absPos).add(obj.pos); + + if (globalFloatingCounter > 0) { + globalFloatingCounter--; + } + } + else { + // just directly call update() for non renderable object + isDirty = obj.update(dt) || isDirty; + } + } + + return isDirty; + }, + + /** + * @ignore + */ + draw : function (renderer, rect) { + var isFloating = false, + hasTransform = false, + x = 0, + y = 0; + + this.drawCount = 0; + + // save the global context + renderer.save(); + + // adjust position if required (e.g. canvas/window centering) + renderer.translate(this.pos.x, this.pos.y); + + // apply the renderable transformation matrix + if (!this.currentTransform.isIdentity()) { + renderer.transform(this.currentTransform); + } + + // apply the group opacity + renderer.setGlobalAlpha(renderer.globalAlpha() * this.getOpacity()); + + for (var i = this.children.length, obj; i--, (obj = this.children[i]);) { + isFloating = obj.floating === true; + + if ((obj.inViewport || isFloating) && obj.isRenderable) { + + hasTransform = !obj.currentTransform.isIdentity(); + + if (isFloating) { + // translate to screen coordinates + renderer.save(); + renderer.resetTransform(); + } else if (obj.autoTransform === true) { + + // calculate the anchor point + var bounds = obj.getBounds(); + var anchor = obj.anchorPoint; + x = bounds.width * anchor.x; + y = bounds.height * anchor.y; + + if (hasTransform) { + renderer.save(); + obj.currentTransform.translate(x, y); + // apply the object transformation + renderer.transform(obj.currentTransform); + } else { + renderer.translate(x, y); + } + } + + // draw the object + obj.draw(renderer, rect); + + // restore the previous "state" + if (isFloating) { + renderer.restore(); + } else if (obj.autoTransform === true) { + if (hasTransform) { + // restore the save context/global matric + obj.currentTransform.translate(-x, -y); + renderer.restore(); + } else { + // translate back + renderer.translate(-x, -y); + } + } + + this.drawCount++; + } + } + // restore the global context + renderer.restore(); + } + }); + + /** + * Base class for ObjectContainer exception handling. + * @name Error + * @class + * @memberOf me.Container + * @constructor + * @param {String} msg Error message. + */ + me.Container.Error = me.Renderable.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Renderable.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Container.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +(function () { + + /** + * a Generic Object Entity
+ * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the entity object + * @param {Number} y the y coordinates of the entity object + * @param {Object} settings Entity properties, to be defined through Tiled or when calling the entity constructor + * + * @param {Number} settings.width the physical width the entity takes up in game + * @param {Number} settings.height the physical height the entity takes up in game + * @param {String} [settings.name] object entity name + * @param {String} [settings.id] object unique IDs + * @param {Image|String} [settings.image] resource name of a spritesheet to use for the entity renderable component + * @param {Number} [settings.framewidth] width of a single frame in the given spritesheet + * @param {Number} [settings.frameheight] height of a single frame in the given spritesheet + * @param {String} [settings.type] object type + * @param {Number} [settings.collisionMask] Mask collision detection for this object + * @param {{me.Rect[],me.Polygon[],me.Line[],me.Ellipse[]}} [settings.shapes] the initial list of collision shapes (usually populated through Tiled) + */ + me.Entity = me.Renderable.extend( + /** @scope me.Entity.prototype */ + { + /** @ignore */ + init : function (x, y, settings) { + + /** + * The entity renderable component (can be any objects deriving from me.Renderable, like me.Sprite for example) + * @public + * @type me.Renderable + * @name renderable + * @memberOf me.Entity + */ + this.renderable = null; + + // ensure mandatory properties are defined + if ((typeof settings.width !== "number") || (typeof settings.height !== "number")) { + throw new me.Entity.Error("height and width properties are mandatory when passing settings parameters to an object entity"); + } + + // call the super constructor + me.Renderable.prototype.init.apply(this, [x, y, + settings.width, + settings.height]); + + if (settings.image) { + this.renderable = new me.Sprite(0, 0, { + "image" : settings.image, + "framewidth" : ~~(settings.framewidth || settings.width), + "frameheight" : ~~(settings.frameheight || settings.height), + "spacing" : ~~settings.spacing, + "margin" : ~~settings.margin, + "anchorPoint" : settings.anchorPoint + }); + } + + // Update anchorPoint + if (settings.anchorPoint) { + this.anchorPoint.set(settings.anchorPoint.x, settings.anchorPoint.y); + } + + /** + * Entity name
+ * as defined in the Tiled Object Properties + * @public + * @type String + * @name name + * @memberOf me.Entity + */ + this.name = settings.name || ""; + + /** + * object type (as defined in Tiled) + * @public + * @type String + * @name type + * @memberOf me.Entity + */ + this.type = settings.type || ""; + + /** + * object unique ID (as defined in Tiled) + * @public + * @type Number + * @name id + * @memberOf me.Entity + */ + this.id = settings.id || ""; + + /** + * dead/living state of the entity
+ * default value : true + * @public + * @type Boolean + * @name alive + * @memberOf me.Entity + */ + this.alive = true; + + /** + * the entity body object + * @public + * @type me.Body + * @name body + * @memberOf me.Entity + */ + // initialize the default body + var shapes = ( + Array.isArray(settings.shapes) ? + settings.shapes : + [ new me.Rect(0, 0, this.width, this.height) ] + ); + if (this.body) { + this.body.init(this, shapes); + } + else { + this.body = new me.Body(this, shapes); + } + + // ensure the entity bounds and pos are up-to-date + var bounds = this.body.updateBounds(); + + // resize the entity if required + if (this.width === 0 && this.height === 0) { + this.resize(bounds.width, bounds.height); + } + + // set the collision mask if defined + if (typeof(settings.collisionMask) !== "undefined") { + this.body.setCollisionMask(settings.collisionMask); + } + + // set the collision mask if defined + if (typeof(settings.collisionType) !== "undefined") { + if (typeof me.collision.types[settings.collisionType] !== "undefined") { + this.body.collisionType = me.collision.types[settings.collisionType]; + } else { + throw new me.Entity.Error("Invalid value for the collisionType property"); + } + } + + // disable for entities + this.autoTransform = false; + }, + + /** + * return the distance to the specified entity + * @name distanceTo + * @memberOf me.Entity + * @function + * @param {me.Entity} entity Entity + * @return {Number} distance + */ + distanceTo: function (e) { + var a = this.getBounds(); + var b = e.getBounds(); + // the me.Vector2d object also implements the same function, but + // we have to use here the center of both entities + var dx = (a.pos.x + (a.width / 2)) - (b.pos.x + (b.width / 2)); + var dy = (a.pos.y + (a.height / 2)) - (b.pos.y + (b.height / 2)); + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * return the distance to the specified point + * @name distanceToPoint + * @memberOf me.Entity + * @function + * @param {me.Vector2d} vector vector + * @return {Number} distance + */ + distanceToPoint: function (v) { + var a = this.getBounds(); + // the me.Vector2d object also implements the same function, but + // we have to use here the center of both entities + var dx = (a.pos.x + (a.width / 2)) - (v.x); + var dy = (a.pos.y + (a.height / 2)) - (v.y); + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * return the angle to the specified entity + * @name angleTo + * @memberOf me.Entity + * @function + * @param {me.Entity} entity Entity + * @return {Number} angle in radians + */ + angleTo: function (e) { + var a = this.getBounds(); + var b = e.getBounds(); + // the me.Vector2d object also implements the same function, but + // we have to use here the center of both entities + var ax = (b.pos.x + (b.width / 2)) - (a.pos.x + (a.width / 2)); + var ay = (b.pos.y + (b.height / 2)) - (a.pos.y + (a.height / 2)); + return Math.atan2(ay, ax); + }, + + /** + * return the angle to the specified point + * @name angleToPoint + * @memberOf me.Entity + * @function + * @param {me.Vector2d} vector vector + * @return {Number} angle in radians + */ + angleToPoint: function (v) { + var a = this.getBounds(); + // the me.Vector2d object also implements the same function, but + // we have to use here the center of both entities + var ax = (v.x) - (a.pos.x + (a.width / 2)); + var ay = (v.y) - (a.pos.y + (a.height / 2)); + return Math.atan2(ay, ax); + }, + + /** @ignore */ + update : function (dt) { + if (this.renderable) { + return this.renderable.update(dt); + } + return me.Renderable.prototype.update.apply(this, [dt]); + }, + + /** + * update the bounds position when the position is modified + * @private + * @name updateBoundsPos + * @memberOf me.Entity + * @function + */ + updateBoundsPos : function (x, y) { + var _pos = this.body.pos; + me.Renderable.prototype.updateBoundsPos.apply(this, [ + x + _pos.x, + y + _pos.y + ]); + return this.getBounds(); + }, + + /** + * update the bounds position when the body is modified + * @private + * @name onBodyUpdate + * @memberOf me.Entity + * @function + */ + onBodyUpdate : function (pos, w, h) { + var bounds = this.getBounds(); + bounds.pos.setV(this.pos).add(pos); + // XXX: This is called from the constructor, before it gets an ancestor + if (this.ancestor) { + bounds.pos.add(this.ancestor._absPos); + } + bounds.resize(w, h); + }, + + /** + * object draw
+ * not to be called by the end user
+ * called by the game manager on each game loop + * @name draw + * @memberOf me.Entity + * @function + * @protected + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object + **/ + draw : function (renderer) { + // draw the child renderable if defined + var child = this.renderable; + if (child instanceof me.Renderable) { + // draw the child renderable's anchorPoint at the entity's + // anchor point. the entity's anchor point is a scale from + // body position to body width/height + var ax = this.anchorPoint.x * this.body.width, + ay = this.anchorPoint.y * this.body.height; + + var x = this.pos.x + this.body.pos.x + ax, + y = this.pos.y + this.body.pos.y + ay; + + renderer.translate(x, y); + + // apply the child transform, if any + if (child.autoTransform === true && !child.currentTransform.isIdentity()) { + // calculate the anchor point + var bounds = child.getBounds(); + var cx = bounds.width * child.anchorPoint.x; + var cy = bounds.height * child.anchorPoint.y; + + renderer.save(); + + // translate to the anchor point + renderer.translate(cx, cy); + // apply the object transformation + renderer.transform(child.currentTransform); + // translate back + renderer.translate(-cx, -cy); + + // draw the object + child.draw(renderer); + + renderer.restore(); + + } else { + child.draw(renderer); + } + renderer.translate(-x, -y); + } + }, + + /** + * Destroy function
+ * @ignore + */ + destroy : function () { + // free some property objects + if (this.renderable) { + this.renderable.destroy.apply(this.renderable, arguments); + this.renderable = null; + } + this.body.destroy.apply(this.body, arguments); + this.body = null; + + // call the parent destroy method + me.Renderable.prototype.destroy.apply(this, arguments); + }, + + /** + * onDeactivateEvent Notification function
+ * Called by engine before deleting the object + * @name onDeactivateEvent + * @memberOf me.Entity + * @function + */ + onDeactivateEvent : function () { + if (this.renderable && this.renderable.onDeactivateEvent) { + this.renderable.onDeactivateEvent(); + } + }, + + /** + * onCollision callback
+ * triggered in case of collision, when this entity body is being "touched" by another one
+ * @name onCollision + * @memberOf me.Entity + * @function + * @param {me.collision.ResponseObject} response the collision response object + * @param {me.Entity} other the other entity touching this one (a reference to response.a or response.b) + * @return {Boolean} true if the object should respond to the collision (its position and velocity will be corrected) + */ + onCollision : function () { + return false; + } + }); + + /** + * Base class for Entity exception handling. + * @name Error + * @class + * @memberOf me.Entity + * @constructor + * @param {String} msg Error message. + */ + me.Entity.Error = me.Renderable.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Renderable.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Entity.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Screens objects & State machine + * + */ + +(function () { + /** + * A class skeleton for "Screen" Object
+ * every "screen" object (title screen, credits, ingame, etc...) to be managed
+ * through the state manager must inherit from this base class. + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @see me.state + */ + me.ScreenObject = me.Object.extend( + /** @scope me.ScreenObject.prototype */ + { + /** @ignore */ + init: function () {}, + + /** + * Object reset function + * @ignore + */ + reset : function () { + // reset the game manager + me.game.reset(); + // call the onReset Function + this.onResetEvent.apply(this, arguments); + }, + + /** + * destroy function + * @ignore + */ + destroy : function () { + // notify the object + this.onDestroyEvent.apply(this, arguments); + }, + + /** + * onResetEvent function
+ * called by the state manager when reseting the object
+ * this is typically where you will load a level, etc... + * to be extended + * @name onResetEvent + * @memberOf me.ScreenObject + * @function + * @param {} [arguments...] optional arguments passed when switching state + * @see me.state#change + */ + onResetEvent : function () { + // to be extended + }, + + /** + * onDestroyEvent function
+ * called by the state manager before switching to another state
+ * @name onDestroyEvent + * @memberOf me.ScreenObject + * @function + */ + onDestroyEvent : function () { + // to be extended + } + }); + + // based on the requestAnimationFrame polyfill by Erik MÃļller + (function () { + var lastTime = 0; + var frameDuration = 1000 / 60; + // get unprefixed rAF and cAF, if present + var requestAnimationFrame = me.agent.prefixed("requestAnimationFrame"); + var cancelAnimationFrame = me.agent.prefixed("cancelAnimationFrame") || + me.agent.prefixed("cancelRequestAnimationFrame"); + + if (!requestAnimationFrame || !cancelAnimationFrame) { + requestAnimationFrame = function (callback) { + var currTime = window.performance.now(); + var timeToCall = Math.max(0, frameDuration - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + cancelAnimationFrame = function (id) { + window.clearTimeout(id); + }; + } + + // put back in global namespace + window.requestAnimationFrame = requestAnimationFrame; + window.cancelAnimationFrame = cancelAnimationFrame; + }()); + + + /** + * a State Manager (state machine)

+ * There is no constructor function for me.state. + * @namespace me.state + * @memberOf me + */ + + me.state = (function () { + // hold public stuff in our singleton + var api = {}; + + /*------------------------------------------- + PRIVATE STUFF + --------------------------------------------*/ + + // current state + var _state = -1; + + // requestAnimeFrame Id + var _animFrameId = -1; + + // whether the game state is "paused" + var _isPaused = false; + + // list of screenObject + var _screenObject = {}; + + // fading transition parameters between screen + var _fade = { + color : "", + duration : 0 + }; + + // callback when state switch is done + /** @ignore */ + var _onSwitchComplete = null; + + // just to keep track of possible extra arguments + var _extraArgs = null; + + // store the elapsed time during pause/stop period + var _pauseTime = 0; + + /** + * @ignore + */ + function _startRunLoop() { + // ensure nothing is running first and in valid state + if ((_animFrameId === -1) && (_state !== -1)) { + // reset the timer + me.timer.reset(); + + // start the main loop + _animFrameId = window.requestAnimationFrame(_renderFrame); + } + } + + /** + * Resume the game loop after a pause. + * @ignore + */ + function _resumeRunLoop() { + // ensure game is actually paused and in valid state + if (_isPaused && (_state !== -1)) { + // reset the timer + me.timer.reset(); + + _isPaused = false; + } + } + + /** + * Pause the loop for most screen objects. + * @ignore + */ + function _pauseRunLoop() { + // Set the paused boolean to stop updates on (most) entities + _isPaused = true; + } + + /** + * this is only called when using requestAnimFrame stuff + * @param {Number} time current timestamp in milliseconds + * @ignore + */ + function _renderFrame(time) { + // update all game objects + me.game.update(time); + // render all game objects + me.game.draw(); + // schedule the next frame update + if (_animFrameId !== -1) { + _animFrameId = window.requestAnimationFrame(_renderFrame); + } + } + + /** + * stop the SO main loop + * @ignore + */ + function _stopRunLoop() { + // cancel any previous animationRequestFrame + window.cancelAnimationFrame(_animFrameId); + _animFrameId = -1; + } + + /** + * start the SO main loop + * @ignore + */ + function _switchState(state) { + // clear previous interval if any + _stopRunLoop(); + + // call the screen object destroy method + if (_screenObject[_state]) { + // just notify the object + _screenObject[_state].screen.destroy(); + } + + if (_screenObject[state]) { + // set the global variable + _state = state; + + // call the reset function with _extraArgs as arguments + _screenObject[_state].screen.reset.apply(_screenObject[_state].screen, _extraArgs); + + // and start the main loop of the + // new requested state + _startRunLoop(); + + // execute callback if defined + if (_onSwitchComplete) { + _onSwitchComplete(); + } + + // force repaint + me.game.repaint(); + } + } + + /* + * PUBLIC STUFF + */ + + /** + * default state ID for Loading Screen + * @constant + * @name LOADING + * @memberOf me.state + */ + api.LOADING = 0; + + /** + * default state ID for Menu Screen + * @constant + * @name MENU + * @memberOf me.state + */ + + api.MENU = 1; + /** + * default state ID for "Ready" Screen + * @constant + * @name READY + * @memberOf me.state + */ + + api.READY = 2; + /** + * default state ID for Play Screen + * @constant + * @name PLAY + * @memberOf me.state + */ + + api.PLAY = 3; + /** + * default state ID for Game Over Screen + * @constant + * @name GAMEOVER + * @memberOf me.state + */ + + api.GAMEOVER = 4; + /** + * default state ID for Game End Screen + * @constant + * @name GAME_END + * @memberOf me.state + */ + + api.GAME_END = 5; + /** + * default state ID for High Score Screen + * @constant + * @name SCORE + * @memberOf me.state + */ + + api.SCORE = 6; + /** + * default state ID for Credits Screen + * @constant + * @name CREDITS + * @memberOf me.state + */ + + api.CREDITS = 7; + /** + * default state ID for Settings Screen + * @constant + * @name SETTINGS + * @memberOf me.state + */ + api.SETTINGS = 8; + + /** + * default state ID for user defined constants
+ * @constant + * @name USER + * @memberOf me.state + * @example + * var STATE_INFO = me.state.USER + 0; + * var STATE_WARN = me.state.USER + 1; + * var STATE_ERROR = me.state.USER + 2; + * var STATE_CUTSCENE = me.state.USER + 3; + */ + api.USER = 100; + + /** + * onPause callback + * @function + * @name onPause + * @memberOf me.state + */ + api.onPause = null; + + /** + * onResume callback + * @function + * @name onResume + * @memberOf me.state + */ + api.onResume = null; + + /** + * onStop callback + * @function + * @name onStop + * @memberOf me.state + */ + api.onStop = null; + + /** + * onRestart callback + * @function + * @name onRestart + * @memberOf me.state + */ + api.onRestart = null; + + /** + * @ignore + */ + api.init = function () { + // set the embedded loading screen + api.set(api.LOADING, new me.DefaultLoadingScreen()); + }; + + /** + * Stop the current screen object. + * @name stop + * @memberOf me.state + * @public + * @function + * @param {Boolean} pauseTrack pause current track on screen stop. + */ + api.stop = function (music) { + // only stop when we are not loading stuff + if ((_state !== api.LOADING) && api.isRunning()) { + // stop the main loop + _stopRunLoop(); + // current music stop + if (music === true) { + me.audio.pauseTrack(); + } + + // store time when stopped + _pauseTime = window.performance.now(); + + // publish the stop notification + me.event.publish(me.event.STATE_STOP); + // any callback defined ? + if (typeof(api.onStop) === "function") { + api.onStop(); + } + } + }; + + /** + * pause the current screen object + * @name pause + * @memberOf me.state + * @public + * @function + * @param {Boolean} pauseTrack pause current track on screen pause + */ + api.pause = function (music) { + // only pause when we are not loading stuff + if ((_state !== api.LOADING) && !api.isPaused()) { + // stop the main loop + _pauseRunLoop(); + // current music stop + if (music === true) { + me.audio.pauseTrack(); + } + + // store time when paused + _pauseTime = window.performance.now(); + + // publish the pause event + me.event.publish(me.event.STATE_PAUSE); + // any callback defined ? + if (typeof(api.onPause) === "function") { + api.onPause(); + } + } + }; + + /** + * Restart the screen object from a full stop. + * @name restart + * @memberOf me.state + * @public + * @function + * @param {Boolean} resumeTrack resume current track on screen resume + */ + api.restart = function (music) { + if (!api.isRunning()) { + // restart the main loop + _startRunLoop(); + // current music stop + if (music === true) { + me.audio.resumeTrack(); + } + + // calculate the elpased time + _pauseTime = window.performance.now() - _pauseTime; + + // force repaint + me.game.repaint(); + + // publish the restart notification + me.event.publish(me.event.STATE_RESTART, [ _pauseTime ]); + // any callback defined ? + if (typeof(api.onRestart) === "function") { + api.onRestart(); + } + } + }; + + /** + * resume the screen object + * @name resume + * @memberOf me.state + * @public + * @function + * @param {Boolean} resumeTrack resume current track on screen resume + */ + api.resume = function (music) { + if (api.isPaused()) { + // resume the main loop + _resumeRunLoop(); + // current music stop + if (music === true) { + me.audio.resumeTrack(); + } + + // calculate the elpased time + _pauseTime = window.performance.now() - _pauseTime; + + // publish the resume event + me.event.publish(me.event.STATE_RESUME, [ _pauseTime ]); + // any callback defined ? + if (typeof(api.onResume) === "function") { + api.onResume(); + } + } + }; + + /** + * return the running state of the state manager + * @name isRunning + * @memberOf me.state + * @public + * @function + * @return {Boolean} true if a "process is running" + */ + api.isRunning = function () { + return _animFrameId !== -1; + }; + + /** + * Return the pause state of the state manager + * @name isPaused + * @memberOf me.state + * @public + * @function + * @return {Boolean} true if the game is paused + */ + api.isPaused = function () { + return _isPaused; + }; + + /** + * associate the specified state with a screen object + * @name set + * @memberOf me.state + * @public + * @function + * @param {Number} state State ID (see constants) + * @param {me.ScreenObject} so Instantiated ScreenObject to associate + * with state ID + * @example + * var MenuButton = me.GUI_Object.extend({ + * "onClick" : function () { + * // Change to the PLAY state when the button is clicked + * me.state.change(me.state.PLAY); + * return true; + * } + * }); + * + * var MenuScreen = me.ScreenObject.extend({ + * onResetEvent: function() { + * // Load background image + * me.game.world.addChild( + * new me.ImageLayer(0, 0, { + * image : "bg", + * z: 0 // z-index + * } + * ); + * + * // Add a button + * me.game.world.addChild( + * new MenuButton(350, 200, { "image" : "start" }), + * 1 // z-index + * ); + * + * // Play music + * me.audio.playTrack("menu"); + * }, + * + * "onDestroyEvent" : function () { + * // Stop music + * me.audio.stopTrack(); + * } + * }); + * + * me.state.set(me.state.MENU, new MenuScreen()); + */ + api.set = function (state, so) { + _screenObject[state] = {}; + _screenObject[state].screen = so; + _screenObject[state].transition = true; + }; + + /** + * return a reference to the current screen object
+ * useful to call a object specific method + * @name current + * @memberOf me.state + * @public + * @function + * @return {me.ScreenObject} + */ + api.current = function () { + return _screenObject[_state].screen; + }; + + /** + * specify a global transition effect + * @name transition + * @memberOf me.state + * @public + * @function + * @param {String} effect (only "fade" is supported for now) + * @param {me.Color|String} color a CSS color value + * @param {Number} [duration=1000] expressed in milliseconds + */ + api.transition = function (effect, color, duration) { + if (effect === "fade") { + _fade.color = color; + _fade.duration = duration; + } + }; + + /** + * enable/disable transition for a specific state (by default enabled for all) + * @name setTransition + * @memberOf me.state + * @public + * @function + * @param {Number} state State ID (see constants) + * @param {Boolean} enable + */ + api.setTransition = function (state, enable) { + _screenObject[state].transition = enable; + }; + + /** + * change the game/app state + * @name change + * @memberOf me.state + * @public + * @function + * @param {Number} state State ID (see constants) + * @param {} [arguments...] extra arguments to be passed to the reset functions + * @example + * // The onResetEvent method on the play screen will receive two args: + * // "level_1" and the number 3 + * me.state.change(me.state.PLAY, "level_1", 3); + */ + api.change = function (state) { + // Protect against undefined ScreenObject + if (typeof(_screenObject[state]) === "undefined") { + throw new me.Error("Undefined ScreenObject for state '" + state + "'"); + } + + if (api.isCurrent(state)) { + // do nothing if already the current state + return; + } + + _extraArgs = null; + if (arguments.length > 1) { + // store extra arguments if any + _extraArgs = Array.prototype.slice.call(arguments, 1); + } + // if fading effect + if (_fade.duration && _screenObject[state].transition) { + /** @ignore */ + _onSwitchComplete = function () { + me.game.viewport.fadeOut(_fade.color, _fade.duration); + }; + me.game.viewport.fadeIn( + _fade.color, + _fade.duration, + function () { + _switchState.defer(this, state); + } + ); + + } + // else just switch without any effects + else { + // wait for the last frame to be + // "finished" before switching + _switchState.defer(this, state); + } + }; + + /** + * return true if the specified state is the current one + * @name isCurrent + * @memberOf me.state + * @public + * @function + * @param {Number} state State ID (see constants) + */ + api.isCurrent = function (state) { + return _state === state; + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ +(function () { + // a basic progress bar object + var ProgressBar = me.Renderable.extend({ + /** + * @ignore + */ + init: function (v, w, h) { + me.Renderable.prototype.init.apply(this, [v.x, v.y, w, h]); + // flag to know if we need to refresh the display + this.invalidate = false; + + // default progress bar height + this.barHeight = 4; + + // current progress + this.progress = 0; + }, + + /** + * make sure the screen is refreshed every frame + * @ignore + */ + onProgressUpdate : function (progress) { + this.progress = ~~(progress * this.width); + this.invalidate = true; + }, + + /** + * @ignore + */ + update : function () { + if (this.invalidate === true) { + // clear the flag + this.invalidate = false; + // and return true + return true; + } + // else return false + return false; + }, + + /** + * draw function + * @ignore + */ + draw : function (renderer) { + // draw the progress bar + renderer.setColor("black"); + renderer.fillRect(0, (this.height / 2) - (this.barHeight / 2), this.width, this.barHeight); + + renderer.setColor("#55aa00"); + renderer.fillRect(2, (this.height / 2) - (this.barHeight / 2), this.progress, this.barHeight); + + renderer.setColor("white"); + } + }); + + // the melonJS Logo + var IconLogo = me.Renderable.extend({ + /** + * @ignore + */ + init : function (x, y) { + me.Renderable.prototype.init.apply(this, [x, y, 100, 85]); + + this.iconCanvas = me.video.createCanvas( + me.utils.nextPowerOfTwo(this.width), + me.utils.nextPowerOfTwo(this.height), + false); + + + var context = me.video.renderer.getContext2d(this.iconCanvas); + + context.beginPath(); + context.moveTo(0.7, 48.9); + context.bezierCurveTo(10.8, 68.9, 38.4, 75.8, 62.2, 64.5); + context.bezierCurveTo(86.1, 53.1, 97.2, 27.7, 87.0, 7.7); + context.lineTo(87.0, 7.7); + context.bezierCurveTo(89.9, 15.4, 73.9, 30.2, 50.5, 41.4); + context.bezierCurveTo(27.1, 52.5, 5.2, 55.8, 0.7, 48.9); + context.lineTo(0.7, 48.9); + context.lineTo(0.7, 48.9); + context.closePath(); + context.fillStyle = "rgb(255, 255, 255)"; + context.fill(); + + context.beginPath(); + context.moveTo(84.0, 7.0); + context.bezierCurveTo(87.6, 14.7, 72.5, 30.2, 50.2, 41.6); + context.bezierCurveTo(27.9, 53.0, 6.9, 55.9, 3.2, 48.2); + context.bezierCurveTo(-0.5, 40.4, 14.6, 24.9, 36.9, 13.5); + context.bezierCurveTo(59.2, 2.2, 80.3, -0.8, 84.0, 7.0); + context.lineTo(84.0, 7.0); + context.closePath(); + context.lineWidth = 5.3; + context.strokeStyle = "rgb(255, 255, 255)"; + context.lineJoin = "miter"; + context.miterLimit = 4.0; + context.stroke(); + }, + /** + * @ignore + */ + draw : function (renderer) { + renderer.drawImage(this.iconCanvas, this.pos.x, this.pos.y); + } + }); + + // the melonJS Text Logo + var TextLogo = me.Renderable.extend({ + /** + * @ignore + */ + init : function (w, h) { + me.Renderable.prototype.init.apply(this, [0, 0, w, h]); + + // offscreen cache canvas + this.fontCanvas = me.video.createCanvas(128, 32); + this.drawFont(me.video.renderer.getContext2d(this.fontCanvas)); + }, + + drawFont : function (context) { + var logo1 = new me.Font("century gothic", 32, "white", "middle"); + var logo2 = new me.Font("century gothic", 32, "#55aa00", "middle"); + var logo1_width = 0; + + // configure the font + logo2.bold(); + logo1.textBaseline = logo2.textBaseline = "top"; + + // measure the logo size (using standard 2d context) + context.font = logo1.font; + context.fillStyle = logo1.fillStyle.toRGBA(); + context.textAlign = logo1.textAlign; + context.textBaseline = logo1.textBaseline; + logo1_width = context.measureText("melon").width; + + // calculate the final rendering position + this.pos.x = Math.round((this.width - logo1_width - context.measureText("JS").width) / 2); + this.pos.y = this.height / 2 + 16; + + // use the private _drawFont method to directly draw on the canvas context + logo1._drawFont(context, "melon", 0, 0); + logo2._drawFont(context, "JS", logo1_width, 0); + }, + + /** + * @ignore + */ + draw : function (renderer) { + renderer.drawImage(this.fontCanvas, this.pos.x, this.pos.y); + } + + }); + + /** + * a default loading screen + * @memberOf me + * @ignore + * @constructor + */ + me.DefaultLoadingScreen = me.ScreenObject.extend({ + /** + * call when the loader is resetted + * @ignore + */ + onResetEvent : function () { + // background color + me.game.world.addChild(new me.ColorLayer("background", "#202020", 0), 0); + + // progress bar + var progressBar = new ProgressBar( + new me.Vector2d(), + me.video.renderer.getWidth(), + me.video.renderer.getHeight() + ); + + this.loaderHdlr = me.event.subscribe( + me.event.LOADER_PROGRESS, + progressBar.onProgressUpdate.bind(progressBar) + ); + + this.resizeHdlr = me.event.subscribe( + me.event.VIEWPORT_ONRESIZE, + progressBar.resize.bind(progressBar) + ); + + me.game.world.addChild(progressBar, 1); + // melonJS text & logo + var icon = new IconLogo( + (me.video.renderer.getWidth() - 100) / 2, + (me.video.renderer.getHeight() / 2) - (progressBar.barHeight / 2) - 90 + ); + me.game.world.addChild(icon, 1); + me.game.world.addChild(new TextLogo(me.video.renderer.getWidth(), me.video.renderer.getHeight()), 1); + }, + + /** + * destroy object at end of loading + * @ignore + */ + onDestroyEvent : function () { + // cancel the callback + me.event.unsubscribe(this.loaderHdlr); + me.event.unsubscribe(this.resizeHdlr); + this.loaderHdlr = this.resizeHdlr = null; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a small class to manage loading of stuff and manage resources + * There is no constructor function for me.input. + * @namespace me.loader + * @memberOf me + */ + me.loader = (function () { + // hold public stuff in our singleton + var api = {}; + + // contains all the images loaded + var imgList = {}; + // contains all the TMX loaded + var tmxList = {}; + // contains all the binary files loaded + var binList = {}; + // contains all the JSON files + var jsonList = {}; + // baseURL + var baseURL = {}; + + // flag to check loading status + var resourceCount = 0; + var loadCount = 0; + var timerId = 0; + + /** + * check the loading status + * @ignore + */ + function checkLoadStatus(onload) { + if (loadCount === resourceCount) { + // wait 1/2s and execute callback (cheap workaround to ensure everything is loaded) + if (onload || api.onload) { + // make sure we clear the timer + clearTimeout(timerId); + // trigger the onload callback + // we call either the supplied callback (which takes precedence) or the global one + var callback = onload || api.onload; + setTimeout(function () { + callback(); + me.event.publish(me.event.LOADER_COMPLETE); + }, 300); + } + else { + console.error("no load callback defined"); + } + } + else { + timerId = setTimeout(function() { + checkLoadStatus(onload); + }, 100); + } + } + + /** + * load Images + * @example + * preloadImages([ + * { name : 'image1', src : 'images/image1.png'}, + * { name : 'image2', src : 'images/image2.png'}, + * { name : 'image3', src : 'images/image3.png'}, + * { name : 'image4', src : 'images/image4.png'} + * ]); + * @ignore + */ + function preloadImage(img, onload, onerror) { + // create new Image object and add to list + imgList[img.name] = new Image(); + imgList[img.name].onload = onload; + imgList[img.name].onerror = onerror; + if (typeof (api.crossOrigin) === "string") { + imgList[img.name].crossOrigin = api.crossOrigin; + } + imgList[img.name].src = img.src + api.nocache; + } + + /** + * preload TMX files + * @ignore + */ + function preloadTMX(tmxData, onload, onerror) { + function addToTMXList(data) { + // set the TMX content + tmxList[tmxData.name] = data; + + // add the tmx to the levelDirector + if (tmxData.type === "tmx") { + me.levelDirector.addTMXLevel(tmxData.name); + } + } + + + //if the data is in the tmxData object, don't get it via a XMLHTTPRequest + if (tmxData.data) { + addToTMXList(tmxData.data); + onload(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + // check the data format ('tmx', 'json') + var format = me.utils.getFileExtension(tmxData.src); + + if (xmlhttp.overrideMimeType) { + if (format === "json") { + xmlhttp.overrideMimeType("application/json"); + } + else { + xmlhttp.overrideMimeType("text/xml"); + } + } + + xmlhttp.open("GET", tmxData.src + api.nocache, true); + + + // set the callbacks + xmlhttp.ontimeout = onerror; + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4) { + // status = 0 when file protocol is used, or cross-domain origin, + // (With Chrome use "--allow-file-access-from-files --disable-web-security") + if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) { + var result = null; + + // parse response + switch (format) { + case "xml": + case "tmx": + case "tsx": + // ie9 does not fully implement the responseXML + if (me.device.ua.match(/msie/i) || !xmlhttp.responseXML) { + if (window.DOMParser) { + // manually create the XML DOM + result = (new DOMParser()).parseFromString(xmlhttp.responseText, "text/xml"); + } else { + throw new api.Error("XML file format loading not supported, use the JSON file format instead"); + } + } + else { + result = xmlhttp.responseXML; + } + // converts to a JS object + var data = me.TMXUtils.parse(result); + switch (format) { + case "tmx": + result = data.map; + break; + + case "tsx": + result = data.tilesets[0]; + break; + } + + break; + + case "json": + result = JSON.parse(xmlhttp.responseText); + break; + + default: + throw new api.Error("TMX file format " + format + "not supported !"); + } + + //set the TMX content + addToTMXList(result); + + // fire the callback + onload(); + } + else { + onerror(); + } + } + }; + // send the request + xmlhttp.send(null); + } + + /** + * preload TMX files + * @ignore + */ + function preloadJSON(data, onload, onerror) { + var xmlhttp = new XMLHttpRequest(); + + if (xmlhttp.overrideMimeType) { + xmlhttp.overrideMimeType("application/json"); + } + + xmlhttp.open("GET", data.src + api.nocache, true); + + // set the callbacks + xmlhttp.ontimeout = onerror; + xmlhttp.onreadystatechange = function () { + if (xmlhttp.readyState === 4) { + // status = 0 when file protocol is used, or cross-domain origin, + // (With Chrome use "--allow-file-access-from-files --disable-web-security") + if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) { + // get the Texture Packer Atlas content + jsonList[data.name] = JSON.parse(xmlhttp.responseText); + // fire the callback + onload(); + } + else { + onerror(); + } + } + }; + // send the request + xmlhttp.send(null); + } + + /** + * preload Binary files + * @ignore + */ + function preloadBinary(data, onload, onerror) { + var httpReq = new XMLHttpRequest(); + + // load our file + httpReq.open("GET", data.src + api.nocache, true); + httpReq.responseType = "arraybuffer"; + httpReq.onerror = onerror; + httpReq.onload = function () { + var arrayBuffer = httpReq.response; + if (arrayBuffer) { + var byteArray = new Uint8Array(arrayBuffer); + var buffer = []; + for (var i = 0; i < byteArray.byteLength; i++) { + buffer[i] = String.fromCharCode(byteArray[i]); + } + binList[data.name] = buffer.join(""); + // callback + onload(); + } + }; + httpReq.send(); + } + + /** + * to enable/disable caching + * @ignore + */ + api.nocache = ""; + + /* + * PUBLIC STUFF + */ + + /** + * onload callback + * @public + * @function + * @name onload + * @memberOf me.loader + * @example + * // set a callback when everything is loaded + * me.loader.onload = this.loaded.bind(this); + */ + api.onload = undefined; + + /** + * onProgress callback
+ * each time a resource is loaded, the loader will fire the specified function, + * giving the actual progress [0 ... 1], as argument, and an object describing the resource loaded + * @public + * @function + * @name onProgress + * @memberOf me.loader + * @example + * // set a callback for progress notification + * me.loader.onProgress = this.updateProgress.bind(this); + */ + api.onProgress = undefined; + + + /** + * crossOrigin attribute to configure the CORS requests for Image data element.
+ * By default (that is, when the attribute is not specified), CORS is not used at all.
+ * The "anonymous" keyword means that there will be no exchange of user credentials via cookies,
+ * client-side SSL certificates or HTTP authentication as described in the Terminology section of the CORS specification.
+ * @public + * @type String + * @name crossOrigin + * @memberOf me.loader + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes + * @example + * // allow for cross-origin texture loading in WebGL + * me.loader.crossOrigin = "anonymous"; + * + * // set all ressources to be loaded + * me.loader.preload(game.resources, this.loaded.bind(this)); + */ + api.crossOrigin = undefined; + + /** + * Base class for Loader exception handling. + * @name Error + * @class + * @memberOf me.loader + * @constructor + * @param {String} msg Error message. + */ + api.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.loader.Error"; + } + }); + + /** + * just increment the number of already loaded resources + * @ignore + */ + api.onResourceLoaded = function (res) { + // increment the loading counter + loadCount++; + + // callback ? + var progress = api.getLoadProgress(); + if (api.onProgress) { + // pass the load progress in percent, as parameter + api.onProgress(progress, res); + } + me.event.publish(me.event.LOADER_PROGRESS, [progress, res]); + }; + + /** + * on error callback for image loading + * @ignore + */ + api.onLoadingError = function (res) { + throw new api.Error("Failed loading resource " + res.src); + }; + + /** + * enable the nocache mechanism + * @ignore + */ + api.setNocache = function (enable) { + api.nocache = enable ? "?" + ~~(Math.random() * 10000000) : ""; + }; + + /** + * change the default baseURL for the given asset type.
+ * (this will prepend the asset URL and must finish with a '/') + * @name setBaseURL + * @memberOf me.loader + * @public + * @function + * @param {String} type "*", "audio", binary", "image", "json", "tmx", "tsx" + * @param {String} [url="./"] default base URL + * @example + * // change the base URL relative address + * me.loader.setBaseURL("audio", "data/audio/"); + * // change the base URL absolute address for all object types + * me.loader.setBaseURL("*", "http://myurl.com/") + */ + api.setBaseURL = function (type, url) { + if (type !== "*") { + baseURL[type] = url; + } else { + // "wildcards" + baseURL["audio"] = url; + baseURL["binary"] = url; + baseURL["image"] = url; + baseURL["json"] = url; + baseURL["tmx"] = url; + baseURL["tsx"] = url; + } + }; + + + /** + * set all the specified game resources to be preloaded. + * @name preload + * @memberOf me.loader + * @public + * @function + * @param {Object[]} resources + * @param {String} resources.name internal name of the resource + * @param {String} resources.type "audio", binary", "image", "json", "tmx", "tsx" + * @param {String} resources.src path and/or file name of the resource (for audio assets only the path is required) + * @param {Boolean} [resources.stream] set to true if you don't have to wait for the audio file to be fully downloaded + * @param {function} [onload=me.loader.onload] function to be called when all resources are loaded + * @param {boolean} [switchToLoadState=true] automatically switch to the loading screen + * @example + * game_resources = [ + * // PNG tileset + * {name: "tileset-platformer", type: "image", src: "data/map/tileset.png"}, + * // PNG packed texture + * {name: "texture", type:"image", src: "data/gfx/texture.png"} + * // TSX file + * {name: "meta_tiles", type: "tsx", src: "data/map/meta_tiles.tsx"}, + * // TMX level (XML & JSON) + * {name: "map1", type: "tmx", src: "data/map/map1.json"}, + * {name: "map2", type: "tmx", src: "data/map/map2.tmx"}, + * {name: "map3", type: "tmx", format: "json", data: {"height":15,"layers":[...],"tilewidth":32,"version":1,"width":20}}, + * {name: "map4", type: "tmx", format: "xml", data: {xml representation of tmx}}, + * // audio resources + * {name: "bgmusic", type: "audio", src: "data/audio/"}, + * {name: "cling", type: "audio", src: "data/audio/"}, + * // binary file + * {name: "ymTrack", type: "binary", src: "data/audio/main.ym"}, + * // JSON file (used for texturePacker) + * {name: "texture", type: "json", src: "data/gfx/texture.json"} + * ]; + * ... + * // set all resources to be loaded + * me.loader.preload(game.resources, this.loaded.bind(this)); + */ + api.preload = function (res, onload, switchToLoadState) { + // parse the resources + for (var i = 0; i < res.length; i++) { + resourceCount += api.load( + res[i], + api.onResourceLoaded.bind(api, res[i]), + api.onLoadingError.bind(api, res[i]) + ); + } + // set the onload callback if defined + if (typeof(onload) !== "undefined") { + api.onload = onload; + } + + if (switchToLoadState !== false) { + // swith to the loading screen + me.state.change(me.state.LOADING); + } + + // check load status + checkLoadStatus(onload); + }; + + /** + * Load a single resource (to be used if you need to load additional resource during the game) + * @name load + * @memberOf me.loader + * @public + * @function + * @param {Object} resource + * @param {String} resource.name internal name of the resource + * @param {String} resource.type "audio", binary", "image", "json", "tmx", "tsx" + * @param {String} resource.src path and/or file name of the resource (for audio assets only the path is required) + * @param {Boolean} [resource.stream] set to true if you don't have to wait for the audio file to be fully downloaded + * @param {Function} onload function to be called when the resource is loaded + * @param {Function} onerror function to be called in case of error + * @example + * // load an image asset + * me.loader.load({name: "avatar", type:"image", src: "data/avatar.png"}, this.onload.bind(this), this.onerror.bind(this)); + * + * // start loading music + * me.loader.load({ + * name : "bgmusic", + * type : "audio", + * src : "data/audio/" + * }, function () { + * me.audio.play("bgmusic"); + * }); + */ + api.load = function (res, onload, onerror) { + // transform the url if necessary + if (typeof (baseURL[res.type]) !== "undefined") { + res.src = baseURL[res.type] + res.src; + } + // check ressource type + switch (res.type) { + case "binary": + // reuse the preloadImage fn + preloadBinary.call(this, res, onload, onerror); + return 1; + + case "image": + // reuse the preloadImage fn + preloadImage.call(this, res, onload, onerror); + return 1; + + case "json": + preloadJSON.call(this, res, onload, onerror); + return 1; + + case "tmx": + case "tsx": + preloadTMX.call(this, res, onload, onerror); + return 1; + + case "audio": + me.audio.load(res, !!res.stream, onload, onerror); + return 1; + + default: + throw new api.Error("load : unknown or invalid resource type : " + res.type); + } + }; + + /** + * unload specified resource to free memory + * @name unload + * @memberOf me.loader + * @public + * @function + * @param {Object} resource + * @return {Boolean} true if unloaded + * @example me.loader.unload({name: "avatar", type:"image", src: "data/avatar.png"}); + */ + api.unload = function (res) { + switch (res.type) { + case "binary": + if (!(res.name in binList)) { + return false; + } + + delete binList[res.name]; + return true; + + case "image": + if (!(res.name in imgList)) { + return false; + } + if (typeof(imgList[res.name].dispose) === "function") { + // cocoonJS implements a dispose function to free + // corresponding allocated texture in memory + imgList[res.name].dispose(); + } + delete imgList[res.name]; + return true; + + case "json": + if (!(res.name in jsonList)) { + return false; + } + + delete jsonList[res.name]; + return true; + + case "tmx": + case "tsx": + if (!(res.name in tmxList)) { + return false; + } + + delete tmxList[res.name]; + return true; + + case "audio": + return me.audio.unload(res.name); + + default: + throw new api.Error("unload : unknown or invalid resource type : " + res.type); + } + }; + + /** + * unload all resources to free memory + * @name unloadAll + * @memberOf me.loader + * @public + * @function + * @example me.loader.unloadAll(); + */ + api.unloadAll = function () { + var name; + + // unload all binary resources + for (name in binList) { + if (binList.hasOwnProperty(name)) { + api.unload({ + "name" : name, + "type" : "binary" + }); + } + } + + // unload all image resources + for (name in imgList) { + if (imgList.hasOwnProperty(name)) { + api.unload({ + "name" : name, + "type" : "image" + }); + } + } + + // unload all tmx resources + for (name in tmxList) { + if (tmxList.hasOwnProperty(name)) { + api.unload({ + "name" : name, + "type" : "tmx" + }); + } + } + + // unload all in json resources + for (name in jsonList) { + if (jsonList.hasOwnProperty(name)) { + api.unload({ + "name" : name, + "type" : "json" + }); + } + } + + // unload all audio resources + me.audio.unloadAll(); + }; + + /** + * return the specified TMX/TSX object + * @name getTMX + * @memberOf me.loader + * @public + * @function + * @param {String} tmx name of the tmx/tsx element ("map1"); + * @return {XML|Object} + */ + api.getTMX = function (elt) { + // force as string + elt = "" + elt; + if (elt in tmxList) { + return tmxList[elt]; + } + else { + //console.log ("warning %s resource not yet loaded!",name); + return null; + } + }; + + /** + * return the specified Binary object + * @name getBinary + * @memberOf me.loader + * @public + * @function + * @param {String} name of the binary object ("ymTrack"); + * @return {Object} + */ + api.getBinary = function (elt) { + // force as string + elt = "" + elt; + if (elt in binList) { + return binList[elt]; + } + else { + //console.log ("warning %s resource not yet loaded!",name); + return null; + } + + }; + + /** + * return the specified Image Object + * @name getImage + * @memberOf me.loader + * @public + * @function + * @param {String} Image name of the Image element ("tileset-platformer"); + * @return {Image} + */ + api.getImage = function (elt) { + // force as string + elt = "" + elt; + if (elt in imgList) { + // return the corresponding Image object + return imgList[elt]; + } + else { + //console.log ("warning %s resource not yet loaded!",name); + return null; + } + + }; + + /** + * return the specified JSON Object + * @name getJSON + * @memberOf me.loader + * @public + * @function + * @param {String} Name for the json file to load + * @return {Object} + */ + api.getJSON = function (elt) { + // force as string + elt = "" + elt; + if (elt in jsonList) { + return jsonList[elt]; + } + else { + return null; + } + }; + + /** + * Return the loading progress in percent + * @name getLoadProgress + * @memberOf me.loader + * @public + * @function + * @deprecated use callback instead + * @return {Number} + */ + api.getLoadProgress = function () { + return loadCount / resourceCount; + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Font / Bitmap font + * + * ASCII Table + * http://www.asciitable.com/ + * [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz] + * + * -> first char " " 32d (0x20); + */ +(function () { + + var runits = ["ex", "em", "pt", "px"]; + var toPX = [12, 24, 0.75, 1]; + + /** + * a generic system font object. + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {String} font a CSS font name + * @param {Number|String} size size, or size + suffix (px, em, pt) + * @param {me.Color|String} fillStyle a CSS color value + * @param {String} [textAlign="left"] horizontal alignment + */ + me.Font = me.Renderable.extend( + /** @scope me.Font.prototype */ { + + /** @ignore */ + init : function (font, size, fillStyle, textAlign) { + // private font properties + /** @ignore */ + this.fontSize = new me.Vector2d(); + + /** + * defines the color used to draw the font.
+ * @public + * @type me.Color + * @default black + * @name me.Font#fillStyle + */ + this.fillStyle = new me.Color().copy(fillStyle); + + /** + * defines the color used to draw the font stroke.
+ * @public + * @type me.Color + * @default black + * @name me.Font#strokeStyle + */ + this.strokeStyle = new me.Color(0, 0, 0); + + /** + * sets the current line width, in pixels, when drawing stroke + * @public + * @type Number + * @default 1 + * @name me.Font#lineWidth + */ + this.lineWidth = 1; + + /** + * Set the default text alignment (or justification),
+ * possible values are "left", "right", and "center".
+ * @public + * @type String + * @default "left" + * @name me.Font#textAlign + */ + this.textAlign = textAlign || "left"; + + /** + * Set the text baseline (e.g. the Y-coordinate for the draw operation),
+ * possible values are "top", "hanging, "middle, "alphabetic, "ideographic, "bottom"
+ * @public + * @type String + * @default "top" + * @name me.Font#textBaseline + */ + this.textBaseline = "top"; + + /** + * Set the line spacing height (when displaying multi-line strings).
+ * Current font height will be multiplied with this value to set the line height. + * @public + * @type Number + * @default 1.0 + * @name me.Font#lineHeight + */ + this.lineHeight = 1.0; + + // super constructor + me.Renderable.prototype.init.apply(this, [0, 0, 0, 0]); + + // font name and type + this.setFont(font, size, fillStyle, textAlign); + + if (!this.gid) { + this.gid = me.utils.createGUID(); + } + }, + + /** + * make the font bold + * @name bold + * @memberOf me.Font + * @function + */ + bold : function () { + this.font = "bold " + this.font; + }, + + /** + * make the font italic + * @name italic + * @memberOf me.Font + * @function + */ + italic : function () { + this.font = "italic " + this.font; + }, + + /** + * Change the font settings + * @name setFont + * @memberOf me.Font + * @function + * @param {String} font a CSS font name + * @param {Number|String} size size, or size + suffix (px, em, pt) + * @param {me.Color|String} [fillStyle] a CSS color value + * @param {String} [textAlign="left"] horizontal alignment + * @example + * font.setFont("Arial", 20, "white"); + * font.setFont("Arial", "1.5em", "white"); + */ + setFont : function (font, size, fillStyle, textAlign) { + // font name and type + var font_names = font.split(",").map(function (value) { + value = value.trim(); + return ( + !/(^".*"$)|(^'.*'$)/.test(value) + ) ? "\"" + value + "\"" : value; + }); + + if (typeof size === "number") { + this.fontSize.y = size; + size += "px"; + } else /* string */ { + // extract the units and convert if necessary + var CSSval = size.match(/([-+]?[\d.]*)(.*)/); + this.fontSize.y = parseFloat(CSSval[1]); + if (CSSval[2]) { + this.fontSize.y *= toPX[runits.indexOf(CSSval[2])]; + } else { + // no unit define, assume px + size += "px"; + } + } + this.height = this.fontSize.y; + + this.font = size + " " + font_names.join(","); + if (typeof(fillStyle) !== "undefined") { + this.fillStyle.copy(fillStyle); + } + if (textAlign) { + this.textAlign = textAlign; + } + }, + + /** + * measure the given text size in pixels + * @name measureText + * @memberOf me.Font + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance + * @param {String} text + * @return {Object} returns an object, with two attributes: width (the width of the text) and height (the height of the text). + */ + measureText : function (renderer, text) { + var context = renderer.getFontContext(); + + // draw the text + context.font = this.font; + context.fillStyle = this.fillStyle.toRGBA(); + context.textAlign = this.textAlign; + context.textBaseline = this.textBaseline; + + this.height = this.width = 0; + + var strings = ("" + text).split("\n"); + for (var i = 0; i < strings.length; i++) { + this.width = Math.max(context.measureText(strings[i].trimRight()).width, this.width); + this.height += this.fontSize.y * this.lineHeight; + } + return { + width : this.width, + height : this.height + }; + }, + + /** + * draw a text at the specified coord + * @name draw + * @memberOf me.Font + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance + * @param {String} text + * @param {Number} x + * @param {Number} y + */ + draw : function (renderer, text, x, y) { + // save the previous global alpha value + var _alpha = renderer.globalAlpha(); + + renderer.setGlobalAlpha(_alpha * this.getOpacity()); + + // draw the text + renderer.drawFont(this._drawFont(renderer.getFontContext(), text, ~~x, ~~y, false)); + + // restore the previous global alpha value + renderer.setGlobalAlpha(_alpha); + }, + + /** + * draw a stroke text at the specified coord, as defined
+ * by the `lineWidth` and `fillStroke` properties.
+ * Note : using drawStroke is not recommended for performance reasons + * @name drawStroke + * @memberOf me.Font + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance + * @param {String} text + * @param {Number} x + * @param {Number} y + */ + drawStroke : function (renderer, text, x, y) { + // save the previous global alpha value + var _alpha = renderer.globalAlpha(); + + renderer.setGlobalAlpha(_alpha * this.getOpacity()); + + // draw the text + renderer.drawFont(this._drawFont(renderer.getFontContext(), text, ~~x, ~~y, true)); + + // restore the previous global alpha value + renderer.setGlobalAlpha(_alpha); + }, + + /** + * @ignore + */ + _drawFont : function (context, text, x, y, stroke) { + context.font = this.font; + context.fillStyle = this.fillStyle.toRGBA(); + if (stroke) { + context.strokeStyle = this.strokeStyle.toRGBA(); + context.lineWidth = this.lineWidth; + } + context.textAlign = this.textAlign; + context.textBaseline = this.textBaseline; + + var strings = ("" + text).split("\n"), string = ""; + var dw = 0; + var dy = y; + var lineHeight = this.fontSize.y * this.lineHeight; + for (var i = 0; i < strings.length; i++) { + string = strings[i].trimRight(); + // measure the string + dw = Math.max(dw, context.measureText(string).width); + // draw the string + context[stroke ? "strokeText" : "fillText"](string, x, y); + // add leading space + y += lineHeight; + } + + // compute bounds + // TODO : memoize me ! + var dx = (this.textAlign === "right" ? x - dw : ( + this.textAlign === "center" ? x - ~~(dw / 2) : x + )); + dy = (this.textBaseline.search(/^(top|hanging)$/) === 0) ? dy : ( + this.textBaseline === "middle" ? dy - ~~(lineHeight / 2) : dy - lineHeight + ); + + // update the renderable bounds + return this.getBounds().setShape( + ~~dx, + ~~dy, + ~~(dw + 0.5), + ~~(strings.length * lineHeight + 0.5) + ); + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Font / Bitmap font + * + * -> first char " " 32d (0x20); + */ +(function () { + + /** + * Measures a single line of text, does not account for \n + * @ignore + */ + var measureTextWidth = function(font, text) { + var characters = text.split(""); + var width = 0; + var lastGlyph = null; + for (var i = 0; i < characters.length; i++) { + var ch = characters[i].charCodeAt(0); + var glyph = font.bitmapFontData.glyphs[ch]; + var kerning = (lastGlyph && lastGlyph.kerning) ? lastGlyph.getKerning(ch) : 0; + width += (glyph.xadvance + kerning) * font.fontScale.x; + lastGlyph = glyph; + } + + return width; + }; + + /** + * a bitmap font object + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Object} font the font object data. Should be retrieved from the loader + * @param {Image} image the font image itself Should be retrieved from the loader + * @param {Number} [scale=1.0] + * @param {String} [textAlign="left"] + * @param {String} [textBaseline="top"] + * @example + * // Use me.loader.preload or me.loader.load to load assets + * me.loader.preload([ + * { name: "arial", type: "binary" src: "data/font/arial.fnt" }, + * { name: "arial", type: "image" src: "data/font/arial.png" }, + * ]) + * // Then create an instance of your bitmap font: + * var myFont = new me.BitmapFont(me.loader.getBinary("arial"), me.loader.getImage("arial")); + * // And draw it inside your Renderable, just like me.Font + * myFont.draw(renderer, "Hello!", 0, 0); + */ + me.BitmapFont = me.Renderable.extend( + /** @scope me.BitmapFont.prototype */ { + /** @ignore */ + init : function (data, fontImage, scale, textAlign, textBaseline) { + /** @ignore */ + // scaled font size; + this.sSize = me.pool.pull("me.Vector2d", 0, 0); + + this.fontImage = fontImage; + + /** + * The instance of me.BitmapFontData + * @type {me.BitmapFontData} + * @name bitmapFontData + * @memberOf me.BitmapFont + */ + this.bitmapFontData = new me.BitmapFontData(data); + this.fontScale = me.pool.pull("me.Vector2d", 1, 1); + + this.charCount = 0; + me.Renderable.prototype.init.apply(this, [0, 0, 0, 0, 0, 0]); + + // set a default alignement + this.textAlign = textAlign || "left"; + this.textBaseline = textBaseline || "top"; + this.lineHeight = 1; + // resize if necessary + if (scale) { + this.resize(scale); + } + }, + + /** + * change the font settings + * @name set + * @memberOf me.BitmapFont + * @function + * @param {String} textAlign ("left", "center", "right") + * @param {Number} [scale] + */ + set : function (textAlign, scale) { + this.textAlign = textAlign; + // updated scaled Size + if (scale) { + this.resize(scale); + } + }, + + /** + * change the font display size + * @name resize + * @memberOf me.BitmapFont + * @function + * @param {Number} scale ratio + */ + resize : function (scale) { + this.fontScale.set(scale, scale); + }, + + + /** + * measure the given text size in pixels + * @name measureText + * @memberOf me.BitmapFont + * @function + * @param {String} text + * @returns {Object} an object with two properties: `width` and `height`, defining the output dimensions + */ + measureText : function (text) { + var strings = ("" + text).split("\n"); + var width = 0; + var height = 0; + var stringHeight = this.bitmapFontData.capHeight * this.lineHeight; + for (var i = 0; i < strings.length; i++) { + width = Math.max(measureTextWidth(this, strings[i]), width); + height += stringHeight; + } + + return {width: width, height: height * this.fontScale.y}; + }, + + /** + * draw a text at the specified coord + * @name draw + * @memberOf me.BitmapFont + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance + * @param {String} text + * @param {Number} x + * @param {Number} y + */ + draw : function (renderer, text, x, y) { + var strings = ("" + text).split("\n"); + var lX = x; + var stringHeight = this.bitmapFontData.capHeight * this.lineHeight * this.fontScale.y; + + // save the previous global alpha value + var _alpha = renderer.globalAlpha(); + renderer.setGlobalAlpha(_alpha * this.getOpacity()); + + // update initial position + this.pos.set(x, y, this.pos.z); // TODO : z ? + for (var i = 0; i < strings.length; i++) { + x = lX; + var string = strings[i].trimRight(); + // adjust x pos based on alignment value + var stringWidth = measureTextWidth(this, string); + switch (this.textAlign) { + case "right": + x -= stringWidth; + break; + + case "center": + x -= stringWidth * 0.5; + break; + + default : + break; + } + + // adjust y pos based on alignment value + switch (this.textBaseline) { + case "middle": + y -= stringHeight * 0.5; + break; + + case "ideographic": + case "alphabetic": + case "bottom": + y -= stringHeight; + break; + + default : + break; + } + + // x *= this.fontScale.x; + // y *= this.fontScale.y; + + // draw the string + var lastGlyph = null; + for (var c = 0, len = string.length; c < len; c++) { + // calculate the char index + var ch = string.charCodeAt(c); + var glyph = this.bitmapFontData.glyphs[ch]; + var kerning = (lastGlyph && lastGlyph.kerning) ? lastGlyph.getKerning(ch) : 0; + + // draw it + if (glyph.width !== 0 && glyph.height !== 0) { + // some browser throw an exception when drawing a 0 width or height image + renderer.drawImage(this.fontImage, + glyph.src.x, glyph.src.y, + glyph.width, glyph.height, + x + glyph.offset.x, + y + glyph.offset.y * this.fontScale.y, + glyph.width * this.fontScale.x, glyph.height * this.fontScale.y + ); + } + + // increment position + x += (glyph.xadvance + kerning) * this.fontScale.x; + lastGlyph = glyph; + } + // increment line + y += stringHeight; + } + // restore the previous global alpha value + renderer.setGlobalAlpha(_alpha); + } + }); +})(); + +/** + * Class for storing relevant data from the font file. + * @class me.BitmapFontData + * @memberOf me + * @param data {String} - The bitmap font data pulled from the resource loader using me.loader.getBinary() + * @constructor + */ +me.BitmapFontData = me.Object.extend({ + /** + * @ignore + */ + init: function (data) { + this.padTop = 0; + this.padRight = 0; + this.padBottom = 0; + this.padLeft = 0; + this.lineHeight = 0; + // The distance from the top of most uppercase characters to the baseline. Since the drawing position is the cap height of + // the first line, the cap height can be used to get the location of the baseline. + this.capHeight = 1; + // The distance from the bottom of the glyph that extends the lowest to the baseline. This number is negative. + this.descent = 0; + this.scale = new me.Vector2d(); + + /** + * The map of glyphs, each key is a char code. + * @name glyphs + * @type {Object} + * @memberOf me.BitmapFontData + */ + this.glyphs = {}; + + + this.xChars = ["x", "e", "a", "o", "n", "s", "r", "c", "u", "m", "v", "w", "z"]; + this.capChars = ["M", "N", "B", "D", "C", "E", "F", "K", "A", "G", "H", "I", "J", "L", "O", "P", "Q", "R", "S", + "T", "U", "V", "W", "X", "Y", "Z"]; + + // parse the data + this.parse(data); + }, + + /** + * Creates a glyph to use for the space character + * @private + * @name _createSpaceGlyph + * @memberOf me.BitmapFontData + * @function + */ + _createSpaceGlyph: function () { + var spaceCharCode = " ".charCodeAt(0); + var glyph = this.glyphs[spaceCharCode]; + if (!glyph) { + glyph = me.pool.pull("me.Glyph"); + glyph.id = spaceCharCode; + glyph.xadvance = this._getFirstGlyph().xadvance; + this.glyphs[spaceCharCode] = glyph; + } + }, + + /** + * Gets the first glyph in the map that is not a space character + * @private + * @name _getFirstGlyph + * @memberOf me.BitmapFontData + * @function + * @returns {me.Glyph} + */ + _getFirstGlyph: function () { + var keys = Object.keys(this.glyphs); + for (var i = 0; i < keys.length; i++) { + if (keys[i] > 32) { + return this.glyphs[keys[i]]; + } + } + return null; + }, + + /** + * Gets the value from a string of pairs. For example: one=1 two=2 something=hi. Can accept the regex of /one={d}/ + * and returns the value of d + * @private + * @name _getValueFromPair + * @memberOf me.BitmapFontData + * @function + * @returns {String} + */ + _getValueFromPair: function (string, pattern) { + var value = string.match(pattern); + if (!value) { + throw "Could not find pattern " + pattern + " in string: " + string; + } + + return value[0].split("=")[1]; + }, + + /** + * This parses the font data text and builds a map of glyphs containing the data for each character + * @name parse + * @memberOf me.BitmapFontData + * @function + * @param {String} fontData + */ + parse: function (fontData) { + if (!fontData) { + throw "File containing font data was empty, cannot load the bitmap font."; + } + var lines = fontData.split(/\r\n|\n/); + var padding = fontData.match(/padding\=\d+,\d+,\d+,\d+/g); + if (!padding) { + throw "Padding not found in first line"; + } + var paddingValues = padding[0].split("=")[1].split(","); + this.padTop = parseFloat(paddingValues[0]); + this.padLeft = parseFloat(paddingValues[1]); + this.padBottom = parseFloat(paddingValues[2]); + this.padRight = parseFloat(paddingValues[3]); + + this.lineHeight = parseFloat(this._getValueFromPair(lines[1], /lineHeight\=\d+/g)); + + var baseLine = parseFloat(this._getValueFromPair(lines[1], /base\=\d+/g)); + + var padY = this.padTop + this.padBottom; + + var glyph = null; + + for (var i = 4; i < lines.length; i++) { + var line = lines[i]; + var characterValues = line.split(/=|\s+/); + if (!line || /^kernings/.test(line)) { + continue; + } + if (/^kerning\s/.test(line)) { + var first = parseFloat(characterValues[2]); + var second = parseFloat(characterValues[4]); + var amount = parseFloat(characterValues[6]); + + glyph = this.glyphs[first]; + if (glyph !== null && typeof glyph !== "undefined") { + glyph.setKerning(second, amount); + } + } else { + glyph = me.pool.pull("me.Glyph"); + + var ch = parseFloat(characterValues[2]); + glyph.id = ch; + glyph.src.set(parseFloat(characterValues[4]), parseFloat(characterValues[6])); + glyph.width = parseFloat(characterValues[8]); + glyph.height = parseFloat(characterValues[10]); + var y = parseFloat(characterValues[14]); + glyph.offset.set(parseFloat(characterValues[12]), y); + + glyph.xadvance = parseFloat(characterValues[16]); + + if (glyph.width > 0 && glyph.height > 0) { + this.descent = Math.min(baseLine + glyph.yoffset, this.descent); + } + + this.glyphs[ch] = glyph; + } + } + + this.descent += this.padBottom; + + this._createSpaceGlyph(); + + var xGlyph = null; + for (i = 0; i < this.xChars.length; i++) { + var xChar = this.xChars[i]; + xGlyph = this.glyphs[xChar.charCodeAt(0)]; + if (xGlyph) { + break; + } + } + if (!xGlyph) { + xGlyph = this._getFirstGlyph(); + } + + var capGlyph = null; + for (i = 0; i < this.capChars.length; i++) { + var capChar = this.capChars[i]; + capGlyph = this.glyphs[capChar.charCodeAt(0)]; + if (capGlyph) { + break; + } + } + if (!capGlyph) { + for (var charCode in this.glyphs) { + if (this.glyphs.hasOwnProperty(charCode)) { + glyph = this.glyphs[charCode]; + if (glyph.height === 0 || glyph.width === 0) { + continue; + } + this.capHeight = Math.max(this.capHeight, glyph.height); + } + } + } else { + this.capHeight = capGlyph.height; + } + + this.capHeight -= padY; + } +}); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Glyph + */ +(function() { + var LOG2_PAGE_SIZE = 9; + var PAGE_SIZE = 1 << LOG2_PAGE_SIZE; + /** + * a glyph representing a single character in a font + * @class + * @extends me.Object + * @memberOf me + * @constructor + */ + me.Glyph = me.Object.extend({ + /** + * @ignore + */ + init: function () { + this.src = new me.Vector2d(); + this.offset = new me.Vector2d(); + this.onResetEvent(); + }, + + /** + * @ignore + */ + onResetEvent: function () { + this.id = 0; + this.src.set(0, 0); + this.width = 0; + this.height = 0; + this.u = 0; + this.v = 0; + this.u2 = 0; + this.v2 = 0; + this.offset.set(0, 0); + this.xadvance = 0; + this.fixedWidth = false; + }, + + /** + * @ignore + */ + getKerning: function (ch) { + if (this.kerning) { + var page = this.kerning[ch >>> LOG2_PAGE_SIZE]; + if (page) { + return page[ch & PAGE_SIZE - 1] || 0; + } + } + return 0; + }, + + /** + * @ignore + */ + setKerning: function (ch, value) { + if (!this.kerning) { + this.kerning = {}; + } + var page = this.kerning[ch >>> LOG2_PAGE_SIZE]; + if (typeof page === "undefined") { + this.kerning[ch >>> LOG2_PAGE_SIZE] = {}; + page = this.kerning[ch >>> LOG2_PAGE_SIZE]; + } + page[ch & PAGE_SIZE - 1] = value; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Audio Mngt Objects + * + * + */ +(function () { + /** + * There is no constructor function for me.audio. + * @namespace me.audio + * @memberOf me + */ + me.audio = (function () { + /* + * PRIVATE STUFF + */ + + // hold public stuff in our singleton + var api = {}; + + // audio channel list + var audioTracks = {}; + + // current music + var current_track_id = null; + + // a retry counter + var retry_counter = 0; + + /** + * event listener callback on load error + * @ignore + */ + function soundLoadError(sound_name, onerror_cb) { + // check the retry counter + if (retry_counter++ > 3) { + // something went wrong + var errmsg = "melonJS: failed loading " + sound_name; + if (me.sys.stopOnAudioError === false) { + // disable audio + me.audio.disable(); + // call error callback if defined + if (onerror_cb) { + onerror_cb(); + } + // warning + console.log(errmsg + ", disabling audio"); + } + else { + // throw an exception and stop everything ! + throw new me.audio.Error(errmsg); + } + // else try loading again ! + } + else { + audioTracks[sound_name].load(); + } + } + + /* + * PUBLIC STUFF + */ + + /** + * configure and initialize the audio engine
+ * melonJS will try to load audio files corresponding to the browser supported audio format(s)
+ * below is the list of supported file extentions :
+ * "mp3", "mpeg", opus", "ogg", "oga", "wav", "aac", "caf", "m4a", "mp4", "weba", "webm", "dolby", "flac"
+ * keep in mind that not all browsers can play all audio formats, and if no compatible codecs are detected, audio will be disabled. + * @name init + * @memberOf me.audio + * @public + * @function + * @param {String} + * [audioFormat="mp3"] audio format provided + * @return {Boolean} Indicates whether audio initialization was successful + * @example + * // initialize the "sound engine", giving "mp3" and "ogg" as desired audio format + * // i.e. on Safari, the loader will load all audio.mp3 files, + * // on Opera the loader will however load audio.ogg files + * if (!me.audio.init("mp3,ogg")) { + * alert("Sorry but your browser does not support html 5 audio !"); + * return; + * } + */ + api.init = function (audioFormat) { + if (!me.initialized) { + throw new me.audio.Error("me.audio.init() called before engine initialization."); + } + // if no param is given to init we use mp3 by default + audioFormat = typeof audioFormat === "string" ? audioFormat : "mp3"; + // convert it into an array + this.audioFormats = audioFormat.split(","); + + return !Howler.noAudio; + }; + + /** + * enable audio output
+ * only useful if audio supported and previously disabled through + * + * @see me.audio#disable + * @name enable + * @memberOf me.audio + * @public + * @function + */ + api.enable = function () { + this.unmuteAll(); + }; + + /** + * disable audio output + * + * @name disable + * @memberOf me.audio + * @public + * @function + */ + api.disable = function () { + this.muteAll(); + }; + + /** + * Load an audio file.
+ *
+ * sound item must contain the following fields :
+ * - name : name of the sound
+ * - src : source path
+ * @ignore + */ + api.load = function (sound, html5, onload_cb, onerror_cb) { + var urls = []; + if (typeof(this.audioFormats) === "undefined" || this.audioFormats.length === 0) { + throw new me.audio.Error("target audio extension(s) should be set through me.audio.init() before calling the preloader."); + } + for (var i = 0; i < this.audioFormats.length; i++) { + urls.push(sound.src + sound.name + "." + this.audioFormats[i] + me.loader.nocache); + } + audioTracks[sound.name] = new Howl({ + src : urls, + volume : Howler.volume(), + html5 : html5 === true, + /** + * @ignore + */ + onloaderror : function () { + soundLoadError.call(me.audio, sound.name, onerror_cb); + }, + /** + * @ignore + */ + onload : function () { + retry_counter = 0; + if (onload_cb) { + onload_cb(); + } + } + }); + + return 1; + }; + + /** + * play the specified sound + * @name play + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Boolean} [loop=false] loop audio + * @param {Function} [onend] Function to call when sound instance ends playing. + * @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted). + * @return {Number} the sound instance ID. + * @example + * // play the "cling" audio clip + * me.audio.play("cling"); + * // play & repeat the "engine" audio clip + * me.audio.play("engine", true); + * // play the "gameover_sfx" audio clip and call myFunc when finished + * me.audio.play("gameover_sfx", false, myFunc); + * // play the "gameover_sfx" audio clip with a lower volume level + * me.audio.play("gameover_sfx", false, null, 0.5); + */ + api.play = function (sound_name, loop, onend, volume) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + var id = sound.play(); + if (typeof loop === "boolean") { + // arg[0] can take different types in howler 2.0 + sound.loop(loop, id); + } + sound.volume(typeof(volume) === "number" ? volume.clamp(0.0, 1.0) : Howler.volume(), id); + if (typeof(onend) === "function") { + if (loop === true) { + sound.on("end", onend, id); + } + else { + sound.once("end", onend, id); + } + } + return id; + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * Fade a currently playing sound between two volumee. + * @name fade + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} from Volume to fade from (0.0 to 1.0). + * @param {Number} to Volume to fade to (0.0 to 1.0). + * @param {Number} duration Time in milliseconds to fade. + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will fade. + */ + api.fade = function (sound_name, from, to, duration, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + sound.fade(from, to, duration, id); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * get/set the position of playback for a sound. + * @name seek + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [seek] The position to move current playback to (in seconds). + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will changed. + * @return return the current seek position (if no extra parameters were given) + * @example + * // return the current position of the background music + * var current_pos = me.audio.seek("dst-gameforest"); + * // set back the position of the background music to the beginning + * me.audio.seek("dst-gameforest", 0); + */ + api.seek = function (sound_name, seek, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + return sound.seek.apply(sound, Array.prototype.slice.call(arguments, 1)); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * get or set the rate of playback for a sound. + * @name rate + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [rate] playback rate : 0.5 to 4.0, with 1.0 being normal speed. + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will be changed. + * @return return the current playback rate (if no extra parameters were given) + * @example + * // get the playback rate of the background music + * var rate = me.audio.rate("dst-gameforest"); + * // speed up the playback of the background music + * me.audio.rate("dst-gameforest", 2.0); + */ + api.rate = function (sound_name, rate, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + return sound.rate.apply(sound, Array.prototype.slice.call(arguments, 1)); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * stop the specified sound on all channels + * @name stop + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will stop. + * @example + * me.audio.stop("cling"); + */ + api.stop = function (sound_name, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + sound.stop(id); + // remove the defined onend callback (if any defined) + sound.off("end", undefined, id); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * pause the specified sound on all channels
+ * this function does not reset the currentTime property + * @name pause + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will pause. + * @example + * me.audio.pause("cling"); + */ + api.pause = function (sound_name, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + sound.pause(id); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * resume the specified sound on all channels
+ * @name resume + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will resume. + * @example + * // play a audio clip + * var id = me.audio.play("myClip"); + * ... + * // pause it + * me.audio.pause("myClip", id); + * ... + * // resume + * me.audio.resume("myClip", id); + */ + api.resume = function (sound_name, id) { + var sound = audioTracks[sound_name]; + if (sound && typeof sound !== "undefined") { + sound.play(id); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * play the specified audio track
+ * this function automatically set the loop property to true
+ * and keep track of the current sound being played. + * @name playTrack + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio track name - case sensitive + * @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted). + * @return {Number} the sound instance ID. + * @example + * me.audio.playTrack("awesome_music"); + */ + api.playTrack = function (sound_name, volume) { + current_track_id = sound_name; + return me.audio.play( + current_track_id, + true, + null, + volume + ); + }; + + /** + * stop the current audio track + * + * @see me.audio#playTrack + * @name stopTrack + * @memberOf me.audio + * @public + * @function + * @example + * // play a awesome music + * me.audio.playTrack("awesome_music"); + * // stop the current music + * me.audio.stopTrack(); + */ + api.stopTrack = function () { + if (current_track_id !== null) { + audioTracks[current_track_id].stop(); + current_track_id = null; + } + }; + + /** + * pause the current audio track + * + * @name pauseTrack + * @memberOf me.audio + * @public + * @function + * @example + * me.audio.pauseTrack(); + */ + api.pauseTrack = function () { + if (current_track_id !== null) { + audioTracks[current_track_id].pause(); + } + }; + + /** + * resume the previously paused audio track + * + * @name resumeTrack + * @memberOf me.audio + * @public + * @function + * @example + * // play an awesome music + * me.audio.playTrack("awesome_music"); + * // pause the audio track + * me.audio.pauseTrack(); + * // resume the music + * me.audio.resumeTrack(); + */ + api.resumeTrack = function () { + if (current_track_id !== null) { + audioTracks[current_track_id].play(); + } + }; + + /** + * returns the current track Id + * @name getCurrentTrack + * @memberOf me.audio + * @public + * @function + * @return {String} audio track name + */ + api.getCurrentTrack = function () { + return current_track_id; + }; + + /** + * set the default global volume + * @name setVolume + * @memberOf me.audio + * @public + * @function + * @param {Number} volume Float specifying volume (0.0 - 1.0 values accepted). + */ + api.setVolume = function (volume) { + Howler.volume(volume); + }; + + /** + * get the default global volume + * @name getVolume + * @memberOf me.audio + * @public + * @function + * @returns {Number} current volume value in Float [0.0 - 1.0] . + */ + api.getVolume = function () { + return Howler.volume(); + }; + + /** + * mute or unmute the specified sound, but does not pause the playback. + * @name mute + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name - case sensitive + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will mute. + * @param {Boolean} [muted=true] True to mute and false to unmute + * @example + * // mute the background music + * me.audio.mute("awesome_music"); + */ + api.mute = function (sound_name, id, mute) { + // if not defined : true + mute = (typeof(mute) === "undefined" ? true : !!mute); + var sound = audioTracks[sound_name]; + if (sound && typeof(sound) !== "undefined") { + sound.mute(mute, id); + } else { + throw new me.audio.Error("audio clip " + sound_name + " does not exist"); + } + }; + + /** + * unmute the specified sound + * @name unmute + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio clip name + * @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will unmute. + */ + api.unmute = function (sound_name, id) { + api.mute(sound_name, id, false); + }; + + /** + * mute all audio + * @name muteAll + * @memberOf me.audio + * @public + * @function + */ + api.muteAll = function () { + Howler.mute(true); + }; + + /** + * unmute all audio + * @name unmuteAll + * @memberOf me.audio + * @public + * @function + */ + api.unmuteAll = function () { + Howler.mute(false); + }; + + /** + * unload specified audio track to free memory + * + * @name unload + * @memberOf me.audio + * @public + * @function + * @param {String} sound_name audio track name - case sensitive + * @return {Boolean} true if unloaded + * @example + * me.audio.unload("awesome_music"); + */ + api.unload = function (sound_name) { + if (!(sound_name in audioTracks)) { + return false; + } + + // destroy the Howl object + audioTracks[sound_name].unload(); + if (typeof(audioTracks[sound_name].dispose) === "function") { + // cocoonJS implements a dispose function to free + // corresponding allocated audio in memory + audioTracks[sound_name].dispose(); + } + delete audioTracks[sound_name]; + return true; + }; + + /** + * unload all audio to free memory + * + * @name unloadAll + * @memberOf me.audio + * @public + * @function + * @example + * me.audio.unloadAll(); + */ + api.unloadAll = function () { + for (var sound_name in audioTracks) { + if (audioTracks.hasOwnProperty(sound_name)) { + api.unload(sound_name); + } + } + }; + + // return our object + return api; + })(); + + /** + * Base class for audio exception handling. + * @name Error + * @class + * @memberOf me.audio + * @constructor + * @param {String} msg Error message. + */ + me.audio.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.audio.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * video functions + * There is no constructor function for me.video + * @namespace me.video + * @memberOf me + */ + me.video = (function () { + // hold public stuff in our apig + var api = {}; + + // internal variables + var canvas = null; + + var deferResizeId = 0; + + var designRatio = 1; + var designWidth = 0; + var designHeight = 0; + + // max display size + var maxWidth = Infinity; + var maxHeight = Infinity; + + // default video settings + var settings = { + wrapper : undefined, + renderer : 0, // canvas + doubleBuffering : false, + autoScale : false, + scale : 1.0, + scaleMethod : "fit", + transparent : false, + antiAlias : false, + subPixel : false, + verbose : false + }; + + + /** + * Auto-detect the best renderer to use + * @ignore + */ + function autoDetectRenderer(c, width, height, options) { + try { + return new me.WebGLRenderer(c, width, height, options); + } + catch (e) { + return new me.CanvasRenderer(c, width, height, options); + } + } + + /* + * PUBLIC STUFF + */ + + /** + * Base class for Video exception handling. + * @name Error + * @class + * @constructor + * @memberOf me.video + * @param {String} msg Error message. + */ + api.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.video.Error"; + } + }); + + /** + * Select the HTML5 Canvas renderer + * @public + * @name CANVAS + * @memberOf me.video + * @enum {Number} + */ + api.CANVAS = 0; + + /** + * Select the WebGL renderer + * @public + * @name WEBGL + * @memberOf me.video + * @enum {Number} + */ + api.WEBGL = 1; + + /** + * Auto-select the renderer (Attempt WebGL first, with fallback to Canvas) + * @public + * @name AUTO + * @memberOf me.video + * @enum {Number} + */ + api.AUTO = 2; + + /** + * Initialize the "video" system (create a canvas based on the given arguments, and the related renderer).
+ * melonJS support various scaling mode :
+ * - `fit` : Letterboxed; content is scaled to design aspect ratio
+ * - `fill-max` : Canvas is resized to fit maximum design resolution; content is scaled to design aspect ratio
+ * - `flex-height` : Canvas height is resized to fit; content is scaled to design aspect ratio
+ * - `flex-width` : Canvas width is resized to fit; content is scaled to design aspect ratio
+ * - `stretch` : Canvas is resized to fit; content is scaled to screen aspect ratio + * @name init + * @memberOf me.video + * @function + * @param {Number} width the width of the canvas viewport + * @param {Number} height the height of the canvas viewport + * @param {Object} [options] The optional video/renderer parameters + * @param {String} [options.wrapper=document.body] the "div" element name to hold the canvas in the HTML file + * @param {Number} [options.renderer=me.video.CANVAS] renderer to use. + * @param {Boolean} [options.doubleBuffering=false] enable/disable double buffering + * @param {Number|String} [options.scale=1.0] enable scaling of the canvas ('auto' for automatic scaling) + * @param {String} [options.scaleMethod="fit"] ('fit','fill-min','fill-max','flex','flex-width','flex-height','stretch') screen scaling modes + * @param {Boolean} [options.useParentDOMSize=false] on browser devices, limit the canvas width and height to its parent container dimensions as returned by getBoundingClientRect(), + * as opposed to the browser window dimensions + * @param {Boolean} [options.transparent=false] whether to allow transparent pixels in the front buffer (screen) + * @param {Boolean} [options.antiAlias=false] whether to enable or not video scaling interpolation + * @return {Boolean} false if initialization failed (canvas not supported) + * @example + * // init the video with a 640x480 canvas + * me.video.init(640, 480, { + * wrapper : "screen", + * renderer : me.video.CANVAS, + * scale : "auto", + * scaleMethod : "fit", + * doubleBuffering : true + * }); + */ + api.init = function (game_width, game_height, options) { + // ensure melonjs has been properly initialized + if (!me.initialized) { + throw new api.Error("me.video.init() called before engine initialization."); + } + + // revert to default options if not defined + settings = Object.assign(settings, options || {}); + + // sanitize potential given parameters + settings.doubleBuffering = !!(settings.doubleBuffering); + settings.useParentDOMSize = !!(settings.useParentDOMSize); + settings.autoScale = (settings.scale === "auto") || false; + if (settings.scaleMethod.search(/^(fill-(min|max)|fit|flex(-(width|height))?|stretch)$/) !== 0) { + settings.scaleMethod = "fit"; + } + settings.transparent = !!(settings.transparent); + + // override renderer settings if &webgl is defined in the URL + if (me.game.HASH.webgl === true) { + settings.renderer = api.WEBGL; + } + + // normalize scale + settings.scale = (settings.autoScale) ? 1.0 : (+settings.scale || 1.0); + me.sys.scale = new me.Vector2d(settings.scale, settings.scale); + + // force double buffering if scaling is required + if (settings.autoScale || (settings.scale !== 1.0)) { + settings.doubleBuffering = true; + } + + // hold the requested video size ratio + designRatio = game_width / game_height; + designWidth = game_width; + designHeight = game_height; + + // default scaled size value + var game_width_zoom = game_width * me.sys.scale.x; + var game_height_zoom = game_height * me.sys.scale.y; + settings.zoomX = game_width_zoom; + settings.zoomY = game_height_zoom; + + //add a channel for the onresize/onorientationchange event + window.addEventListener( + "resize", + throttle( + 100, + false, + function (event) { + me.event.publish(me.event.WINDOW_ONRESIZE, [ event ]); + } + ), + false + ); + window.addEventListener( + "orientationchange", + function (event) { + me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE, [ event ]); + }, + false + ); + + // register to the channel + me.event.subscribe( + me.event.WINDOW_ONRESIZE, + me.video.onresize.bind(me.video) + ); + me.event.subscribe( + me.event.WINDOW_ONORIENTATION_CHANGE, + me.video.onresize.bind(me.video) + ); + + // create the main screen canvas + if (me.device.ejecta === true) { + // a main canvas is already automatically created by Ejecta + canvas = document.getElementById("canvas"); + } else { + canvas = api.createCanvas(game_width_zoom, game_height_zoom, true); + } + + // add our canvas + if (options.wrapper) { + settings.wrapper = document.getElementById(options.wrapper); + } + // if wrapperid is not defined (null) + if (!settings.wrapper) { + // add the canvas to document.body + settings.wrapper = document.body; + } + settings.wrapper.appendChild(canvas); + + // stop here if not supported + if (!canvas.getContext) { + return false; + } + + /** + * A reference to the current video renderer + * @public + * @memberOf me.video + * @name renderer + * @type {me.Renderer|me.CanvasRenderer|me.WebGLRenderer} + */ + switch (settings.renderer) { + case api.WEBGL: + this.renderer = new me.WebGLRenderer(canvas, game_width, game_height, settings); + break; + case api.AUTO: + this.renderer = autoDetectRenderer(canvas, game_width, game_height, settings); + break; + default: + this.renderer = new me.CanvasRenderer(canvas, game_width, game_height, settings); + break; + } + + // adjust CSS style for High-DPI devices + var ratio = me.device.getPixelRatio(); + if (ratio > 1) { + canvas.style.width = (canvas.width / ratio) + "px"; + canvas.style.height = (canvas.height / ratio) + "px"; + } + + + // set max the canvas max size if CSS values are defined + if (window.getComputedStyle) { + var style = window.getComputedStyle(canvas, null); + me.video.setMaxSize(parseInt(style.maxWidth, 10), parseInt(style.maxHeight, 10)); + } + + me.game.init(); + + // trigger an initial resize(); + me.video.onresize(); + + return true; + }; + + /** + * return the relative (to the page) position of the specified Canvas + * @name getPos + * @memberOf me.video + * @function + * @param {Canvas} [canvas] system one if none specified + * @return {me.Vector2d} + */ + api.getPos = function (c) { + c = c || this.renderer.getScreenCanvas(); + return ( + c.getBoundingClientRect ? + c.getBoundingClientRect() : { left : 0, top : 0 } + ); + }; + + /** + * set the max canvas display size (when scaling) + * @name setMaxSize + * @memberOf me.video + * @function + * @param {Number} width width + * @param {Number} height height + */ + api.setMaxSize = function (w, h) { + // max display size + maxWidth = w || Infinity; + maxHeight = h || Infinity; + // trigger a resize + // defer it to ensure everything is properly intialized + this.onresize.defer(this); + + }; + + /** + * Create and return a new Canvas + * @name createCanvas + * @memberOf me.video + * @function + * @param {Number} width width + * @param {Number} height height + * @param {Boolean} [screencanvas=false] set to true if this canvas renders directly to the screen + * @return {Canvas} + */ + api.createCanvas = function (width, height, screencanvas) { + if (width === 0 || height === 0) { + throw new api.Error("width or height was zero, Canvas could not be initialized !"); + } + + var _canvas = document.createElement("canvas"); + + if ((screencanvas === true) && (me.device.cocoon) && (me.device.android2 !== true)) { + // http://docs.cocoon.io/article/screencanvas/ + _canvas.screencanvas = true; + } + + _canvas.width = width || canvas.width; + _canvas.height = height || canvas.height; + + return _canvas; + }; + + /** + * return a reference to the wrapper + * @name getWrapper + * @memberOf me.video + * @function + * @return {Document} + */ + api.getWrapper = function () { + return settings.wrapper; + }; + + /** + * callback for window resize event + * @ignore + */ + api.onresize = function () { + // default (no scaling) + var scaleX = 1, scaleY = 1; + + // check for orientation information + if (typeof window.orientation !== "undefined") { + me.device.orientation = window.orientation; + } + else { + // is this actually not the best option since default "portrait" + // orientation might vary between for example an ipad and and android tab + me.device.orientation = ( + window.outerWidth > window.outerHeight ? + 90 : 0 + ); + } + + if (settings.autoScale) { + var parentNodeWidth; + var parentNodeHeight; + var parentNode = me.video.renderer.getScreenCanvas().parentNode; + if (typeof (parentNode) !== "undefined") { + if (settings.useParentDOMSize && typeof parentNode.getBoundingClientRect === "function") { + var rect = parentNode.getBoundingClientRect(); + parentNodeWidth = rect.width || (rect.right - rect.left); + parentNodeHeight = rect.height || (rect.bottom - rect.top); + } else { + // for cased where DOM is not implemented and so parentNode (e.g. Ejecta) + parentNodeWidth = parentNode.width; + parentNodeHeight = parentNode.height; + } + } + var _max_width = Math.min(maxWidth, parentNodeWidth || window.innerWidth); + var _max_height = Math.min(maxHeight, parentNodeHeight || window.innerHeight); + var screenRatio = _max_width / _max_height; + var sWidth = Infinity; + var sHeight = Infinity; + + if ( + (settings.scaleMethod === "fill-min" && screenRatio > designRatio) || + (settings.scaleMethod === "fill-max" && screenRatio < designRatio) || + (settings.scaleMethod === "flex-width") + ) { + // resize the display canvas to fill the parent container + sWidth = Math.min(maxWidth, designHeight * screenRatio); + scaleX = scaleY = _max_width / sWidth; + sWidth = ~~(sWidth + 0.5); + this.renderer.resize(sWidth, designHeight); + me.game.viewport.resize(sWidth, designHeight); + /* + * XXX: Workaround for not updating container child-bounds + * automatically (it's expensive!) + */ + me.game.world.updateChildBounds(); + } + else if ( + (settings.scaleMethod === "fill-min" && screenRatio < designRatio) || + (settings.scaleMethod === "fill-max" && screenRatio > designRatio) || + (settings.scaleMethod === "flex-height") + ) { + // resize the display canvas to fill the parent container + sHeight = Math.min(maxHeight, designWidth * (_max_height / _max_width)); + scaleX = scaleY = _max_height / sHeight; + sHeight = ~~(sHeight + 0.5); + this.renderer.resize(designWidth, sHeight); + me.game.viewport.resize(designWidth, sHeight); + /* + * XXX: Workaround for not updating container child-bounds + * automatically (it's expensive!) + */ + me.game.world.updateChildBounds(); + } + else if (settings.scaleMethod === "flex") { + // resize the display canvas to fill the parent container + this.renderer.resize(_max_width, _max_height); + me.game.viewport.resize(_max_width, _max_height); + /* + * XXX: Workaround for not updating container child-bounds + * automatically (it's expensive!) + */ + me.game.world.updateChildBounds(); + } + else if (settings.scaleMethod === "stretch") { + // scale the display canvas to fit with the parent container + scaleX = _max_width / designWidth; + scaleY = _max_height / designHeight; + } + else { + // scale the display canvas to fit the parent container + // make sure we maintain the original aspect ratio + if (screenRatio < designRatio) { + scaleX = scaleY = _max_width / designWidth; + } + else { + scaleX = scaleY = _max_height / designHeight; + } + } + + // adjust scaling ratio based on the device pixel ratio + scaleX *= me.device.getPixelRatio(); + scaleY *= me.device.getPixelRatio(); + + if (deferResizeId) { + // cancel any previous pending resize + clearTimeout(deferResizeId); + } + deferResizeId = me.video.updateDisplaySize.defer(this, scaleX, scaleY); + } + }; + + /** + * Modify the "displayed" canvas size + * @name updateDisplaySize + * @memberOf me.video + * @function + * @param {Number} scaleX X scaling multiplier + * @param {Number} scaleY Y scaling multiplier + */ + api.updateDisplaySize = function (scaleX, scaleY) { + // update the global scale variable + me.sys.scale.set(scaleX, scaleY); + + // renderer resize logic + this.renderer.scaleCanvas(scaleX, scaleY); + me.game.repaint(); + + // make sure we have the correct relative canvas position cached + me.input._offset = me.video.getPos(); + + // clear the timeout id + deferResizeId = 0; + }; + + // return our api + return api; + })(); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +(function () { + + /** + * a base renderer object + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Canvas} canvas The html canvas tag to draw to on screen. + * @param {Number} width The width of the canvas without scaling + * @param {Number} height The height of the canvas without scaling + * @param {Object} [options] The renderer parameters + * @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering + * @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing + * @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled) + * @param {Boolean} [options.subPixel=false] Whether to enable subpixel rendering (performance hit when enabled) + * @param {Boolean} [options.verbose=false] Enable the verbose mode that provides additional details as to what the renderer is doing + * @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied + * @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied + */ + me.Renderer = me.Object.extend( + /** @scope me.Renderer.prototype */ + { + /** + * @ignore + */ + init : function (c, width, height, options) { + options = options || {}; + + // rendering options + this.transparent = !!(options.transparent); + this.doubleBuffering = !!(options.doubleBuffering); + this.antiAlias = !!(options.antiAlias); + this.subPixel = !!(options.subPixel); + this.verbose = !!(options.verbose); + + this.gameWidthZoom = options.zoomX || width; + this.gameHeightZoom = options.zoomY || height; + + // canvas object and context + this.canvas = this.backBufferCanvas = c; + this.context = null; + + // global color + this.currentColor = new me.Color(255, 255, 255, 1.0); + + // default uvOffset + this.uvOffset = 0; + + return this; + }, + + /** + * @ignore + */ + applyRGBFilter : function (object, effect, option) { + //create a output canvas using the given canvas or image size + var _context = this.getContext2d(me.video.createCanvas(object.width, object.height, false)); + // get the pixels array of the give parameter + var imgpix = me.utils.getPixels(object); + // pointer to the pixels data + var pix = imgpix.data; + + // apply selected effect + var i, n; + switch (effect) { + case "b&w": + for (i = 0, n = pix.length; i < n; i += 4) { + var grayscale = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) >>> 3; + pix[i] = grayscale; // red + pix[i + 1] = grayscale; // green + pix[i + 2] = grayscale; // blue + } + break; + + case "brightness": + // make sure it's between 0.0 and 1.0 + var brightness = Math.abs(option).clamp(0.0, 1.0); + for (i = 0, n = pix.length; i < n; i += 4) { + + pix[i] *= brightness; // red + pix[i + 1] *= brightness; // green + pix[i + 2] *= brightness; // blue + } + break; + + case "transparent": + var refColor = me.pool.pull("me.Color").parseCSS(option); + var pixel = me.pool.pull("me.Color"); + for (i = 0, n = pix.length; i < n; i += 4) { + pixel.setColor(pix[i], pix[i + 1], pix[i + 2]); + if (pixel.equals(refColor)) { + pix[i + 3] = 0; + } + } + me.pool.push(refColor); + me.pool.push(pixel); + + break; + + + default: + return null; + } + + // put our modified image back in the new filtered canvas + _context.putImageData(imgpix, 0, 0); + + // return it + return _context; + }, + + /** + * @ignore + */ + clear : function () {}, + + /** + * @ignore + */ + reset : function () { + this.resetTransform(); + this.cache.reset(); + }, + + /** + * return a reference to the system canvas + * @name getCanvas + * @memberOf me.Renderer + * @function + * @return {Canvas} + */ + getCanvas : function () { + return this.backBufferCanvas; + }, + + /** + * return a reference to the screen canvas + * @name getScreenCanvas + * @memberOf me.Renderer + * @function + * @return {Canvas} + */ + getScreenCanvas : function () { + return this.canvas; + }, + + /** + * return a reference to the screen canvas corresponding 2d Context
+ * (will return buffered context if double buffering is enabled, or a reference to the Screen Context) + * @name getScreenContext + * @memberOf me.Renderer + * @function + * @return {Context2d} + */ + getScreenContext : function () { + return this.context; + }, + + /** + * Returns the 2D Context object of the given Canvas
+ * Also configures anti-aliasing based on constructor options. + * @name getContext2d + * @memberOf me.Renderer + * @function + * @param {Canvas} canvas + * @param {Boolean} [opaque=false] True to disable transparency + * @return {Context2d} + */ + getContext2d : function (c, opaque) { + if (typeof c === "undefined" || c === null) { + throw new me.video.Error( + "You must pass a canvas element in order to create " + + "a 2d context" + ); + } + + if (typeof c.getContext === "undefined") { + throw new me.video.Error( + "Your browser does not support HTML5 canvas." + ); + } + + var _context; + if (me.device.cocoon) { + // cocoonJS specific extension + _context = c.getContext("2d", { + "antialias" : this.antiAlias, + "alpha" : !opaque + }); + } + else { + _context = c.getContext("2d", { + "alpha" : !opaque + }); + } + if (!_context.canvas) { + _context.canvas = c; + } + this.setAntiAlias(_context, this.antiAlias); + return _context; + }, + + /** + * return the width of the system Canvas + * @name getWidth + * @memberOf me.Renderer + * @function + * @return {Number} + */ + getWidth : function () { + return this.backBufferCanvas.width; + }, + + /** + * return the height of the system Canvas + * @name getHeight + * @memberOf me.Renderer + * @function + * @return {Number} + */ + getHeight : function () { + return this.backBufferCanvas.height; + }, + + /** + * get the current fill & stroke style color. + * @name getColor + * @memberOf me.Renderer + * @function + * @param {me.Color} current global color + */ + getColor : function () { + return this.currentColor; + }, + + /** + * return the current global alpha + * @name globalAlpha + * @memberOf me.Renderer + * @function + * @return {Number} + */ + globalAlpha : function () { + return this.currentColor.glArray[3]; + }, + + /** + * resizes the system canvas + * @name resize + * @memberOf me.Renderer + * @function + * @param {Number} width new width of the canvas + * @param {Number} height new height of the canvas + */ + resize : function (width, height) { + this.backBufferCanvas.width = width; + this.backBufferCanvas.height = height; + }, + + /** + * enable/disable image smoothing (scaling interpolation) for the specified 2d Context
+ * (!) this might not be supported by all browsers
+ * @name setAntiAlias + * @memberOf me.Renderer + * @function + * @param {Context2d} context + * @param {Boolean} [enable=false] + */ + setAntiAlias : function (context, enable) { + if (typeof(context) !== "undefined") { + // enable/disable antialis on the given context + me.agent.setPrefixed("imageSmoothingEnabled", enable === true, context); + } + + // disable antialias CSS scaling on the main canvas + var cssStyle = context.canvas.style["image-rendering"]; + if (enable === false && (cssStyle === "" || cssStyle === "auto")) { + // if a specific value is set through CSS or equal to the standard "auto" one + context.canvas.style["image-rendering"] = "pixelated"; + } else if (enable === true && cssStyle === "pixelated") { + // if set to the standard "pixelated" + context.canvas.style["image-rendering"] = "auto"; + } + }, + + /** + * @ignore + */ + drawFont : function (/*bounds*/) {} + + }); + +})(); + +/* +* MelonJS Game Engine +* Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod +* http://www.melonjs.org +* +*/ +(function () { + + /** + * a basic texture cache object + * @ignore + */ + me.Renderer.TextureCache = me.Object.extend({ + /** + * @ignore + */ + init : function (max_size) { + this.max_size = max_size || Infinity; + this.reset(); + }, + + /** + * @ignore + */ + reset : function () { + this.cache = new Map(); + this.units = new Map(); + this.length = 0; + }, + + /** + * @ignore + */ + validate : function () { + if (this.length >= this.max_size) { + // TODO: Merge textures instead of throwing an exception + throw new me.video.Error( + "Texture cache overflow: " + this.max_size + + " texture units available." + ); + } + }, + + /** + * @ignore + */ + get : function (image, atlas) { + if (!this.cache.has(image)) { + if (!atlas) { + atlas = me.video.renderer.Texture.prototype.createAtlas.apply( + me.video.renderer.Texture.prototype, + [image.width, image.height, image.src ? me.utils.getBasename(image.src) : undefined] + ); + } + this.put(image, new me.video.renderer.Texture(atlas, image, false)); + } + return this.cache.get(image); + }, + + /** + * @ignore + */ + put : function (image, texture) { + this.validate(); + this.cache.set(image, texture); + this.units.set(texture, this.length++); + }, + + /** + * @ignore + */ + getUnit : function (texture) { + return this.units.get(texture); + } + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016 Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +(function () { + + /** + * a canvas renderer object + * @class + * @extends me.Renderer + * @memberOf me + * @constructor + * @param {Canvas} canvas The html canvas tag to draw to on screen. + * @param {Number} width The width of the canvas without scaling + * @param {Number} height The height of the canvas without scaling + * @param {Object} [options] The renderer parameters + * @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering + * @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing + * @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled) + * @param {Boolean} [options.subPixel=false] Whether to enable subpixel renderering (performance hit when enabled) + * @param {Boolean} [options.textureSeamFix=true] enable the texture seam fix when rendering Tile when antiAlias is off for the canvasRenderer + * @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied + * @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied + */ + me.CanvasRenderer = me.Renderer.extend( + /** @scope me.CanvasRenderer.prototype */ + { + /** + * @ignore + */ + init : function (c, width, height, options) { + // parent constructor + me.Renderer.prototype.init.apply(this, [c, width, height, options]); + + // defined the 2d context + this.context = this.getContext2d(this.canvas, !this.transparent); + + // create the back buffer if we use double buffering + if (this.doubleBuffering) { + this.backBufferCanvas = me.video.createCanvas(width, height, false); + this.backBufferContext2D = this.getContext2d(this.backBufferCanvas); + + if (this.transparent) { + // Clears the front buffer for each frame blit + this.context.globalCompositeOperation = "copy"; + } + } + else { + this.backBufferCanvas = this.canvas; + this.backBufferContext2D = this.context; + } + + // apply the default color to the 2d context + this.setColor(this.currentColor); + + // create a texture cache + this.cache = new me.Renderer.TextureCache(); + + if (options.textureSeamFix !== false && !this.antiAlias) { + // enable the tile texture seam fix with the canvas renderer + this.uvOffset = 1; + } + + return this; + }, + + /** + * prepare the framebuffer for drawing a new frame + * @name clear + * @memberOf me.CanvasRenderer + * @function + */ + clear : function () { + if (this.transparent) { + this.clearColor("rgba(0,0,0,0)", true); + } + }, + + /** + * render the main framebuffer on screen + * @name flush + * @memberOf me.CanvasRenderer + * @function + */ + flush : function () { + if (this.doubleBuffering) { + this.context.drawImage( + this.backBufferCanvas, 0, 0, + this.backBufferCanvas.width, this.backBufferCanvas.height, + 0, 0, + this.gameWidthZoom, this.gameHeightZoom + ); + } + }, + + /** + * Clears the main framebuffer with the given color + * @name clearColor + * @memberOf me.CanvasRenderer + * @function + * @param {me.Color|String} color CSS color. + * @param {Boolean} [opaque=false] Allow transparency [default] or clear the surface completely [true] + */ + clearColor : function (col, opaque) { + var _ctx = this.backBufferContext2D; + var _canvas = _ctx.canvas; + + _ctx.save(); + _ctx.setTransform(1, 0, 0, 1, 0, 0); + _ctx.globalCompositeOperation = opaque ? "copy" : "source-over"; + _ctx.fillStyle = (col instanceof me.Color) ? col.toRGBA() : col; + _ctx.fillRect(0, 0, _canvas.width, _canvas.height); + _ctx.restore(); + }, + + /** + * Sets all pixels in the given rectangle to transparent black,
+ * erasing any previously drawn content. + * @name clearRect + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x x axis of the coordinate for the rectangle starting point. + * @param {Number} y y axis of the coordinate for the rectangle starting point. + * @param {Number} width The rectangle's width. + * @param {Number} height The rectangle's height. + */ + clearRect : function (x, y, width, height) { + this.backBufferContext2D.clearRect(x, y, width, height); + }, + + /** + * Create a pattern with the specified repition + * @name createPattern + * @memberOf me.CanvasRenderer + * @function + * @param {image} image Source image + * @param {String} repeat Define how the pattern should be repeated + * @return {CanvasPattern} + * @see me.ImageLayer#repeat + * @example + * var tileable = renderer.createPattern(image, "repeat"); + * var horizontal = renderer.createPattern(image, "repeat-x"); + * var vertical = renderer.createPattern(image, "repeat-y"); + * var basic = renderer.createPattern(image, "no-repeat"); + */ + createPattern : function (image, repeat) { + return this.backBufferContext2D.createPattern(image, repeat); + }, + + /** + * Draw an image using the canvas api + * @name drawImage + * @memberOf me.CanvasRenderer + * @function + * @param {image} image Source image + * @param {Number} sx Source x-coordinate + * @param {Number} sy Source y-coordinate + * @param {Number} sw Source width + * @param {Number} sh Source height + * @param {Number} dx Destination x-coordinate + * @param {Number} dy Destination y-coordinate + * @param {Number} dw Destination width + * @param {Number} dh Destination height + * @example + * // Can be used in three ways: + * renderer.drawImage(image, dx, dy); + * renderer.drawImage(image, dx, dy, dw, dh); + * renderer.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh); + * // dx, dy, dw, dh being the destination target & dimensions. sx, sy, sw, sh being the position & dimensions to take from the image + */ + drawImage : function (image, sx, sy, sw, sh, dx, dy, dw, dh) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + + if (this.subPixel === false) { + if (typeof sw === "undefined") { + sw = dw = image.width; + sh = dh = image.height; + dx = sx; + dy = sy; + sx = 0; + sy = 0; + } + else if (typeof dx === "undefined") { + dx = sx; + dy = sy; + dw = sw; + dh = sh; + sw = image.width; + sh = image.height; + sx = 0; + sy = 0; + } + this.backBufferContext2D.drawImage(image, sx, sy, sw, sh, ~~dx, ~~dy, dw, dh); + } else { + this.backBufferContext2D.drawImage.apply(this.backBufferContext2D, arguments); + } + }, + + /** + * Draw a pattern within the given rectangle. + * @name drawPattern + * @memberOf me.CanvasRenderer + * @function + * @param {CanvasPattern} pattern Pattern object + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + * @see me.CanvasRenderer#createPattern + */ + drawPattern : function (pattern, x, y, width, height) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + var fillStyle = this.backBufferContext2D.fillStyle; + this.backBufferContext2D.fillStyle = pattern; + this.backBufferContext2D.fillRect(x, y, width, height); + this.backBufferContext2D.fillStyle = fillStyle; + }, + + /** + * Fill an arc at the specified coordinates with given radius, start and end points + * @name fillArc + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x arc center point x-axis + * @param {Number} y arc center point y-axis + * @param {Number} radius + * @param {Number} start start angle in radians + * @param {Number} end end angle in radians + * @param {Boolean} [antiClockwise=false] draw arc anti-clockwise + */ + fillArc : function (x, y, radius, start, end, antiClockwise) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + + this.translate(x + radius, y + radius); + this.backBufferContext2D.beginPath(); + this.backBufferContext2D.arc(0, 0, radius, start, end, antiClockwise || false); + this.backBufferContext2D.fill(); + this.backBufferContext2D.closePath(); + this.translate(- (x + radius), -(y + radius)); + }, + + /** + * Draw a filled rectangle at the specified coordinates + * @name fillRect + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + fillRect : function (x, y, width, height) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + this.backBufferContext2D.fillRect(x, y, width, height); + }, + + /** + * return a reference to the system 2d Context + * @name getContext + * @memberOf me.CanvasRenderer + * @function + * @return {Context2d} + */ + getContext : function () { + return this.backBufferContext2D; + }, + + /** + * return a reference to the font 2d Context + * @ignore + */ + getFontContext : function () { + // in canvas more we can directly use the 2d context + return this.getContext(); + }, + + /** + * resets the canvas transform to identity + * @name resetTransform + * @memberOf me.CanvasRenderer + * @function + */ + resetTransform : function () { + this.backBufferContext2D.setTransform(1, 0, 0, 1, 0, 0); + }, + + /** + * scales the canvas & 2d Context + * @name scaleCanvas + * @memberOf me.CanvasRenderer + * @function + */ + scaleCanvas : function (scaleX, scaleY) { + this.canvas.width = this.gameWidthZoom = this.backBufferCanvas.width * scaleX; + this.canvas.height = this.gameHeightZoom = this.backBufferCanvas.height * scaleY; + + // adjust CSS style for High-DPI devices + if (me.device.getPixelRatio() > 1) { + this.canvas.style.width = (this.canvas.width / me.device.getPixelRatio()) + "px"; + this.canvas.style.height = (this.canvas.height / me.device.getPixelRatio()) + "px"; + } + + if (this.doubleBuffering && this.transparent) { + // Clears the front buffer for each frame blit + this.context.globalCompositeOperation = "copy"; + } + this.setAntiAlias(this.context, this.antiAlias); + this.flush(); + }, + + /** + * save the canvas context + * @name save + * @memberOf me.CanvasRenderer + * @function + */ + save : function () { + this.backBufferContext2D.save(); + }, + + /** + * restores the canvas context + * @name restore + * @memberOf me.CanvasRenderer + * @function + */ + restore : function () { + this.backBufferContext2D.restore(); + this.currentColor.glArray[3] = this.backBufferContext2D.globalAlpha; + }, + + /** + * rotates the canvas context + * @name rotate + * @memberOf me.CanvasRenderer + * @function + * @param {Number} angle in radians + */ + rotate : function (angle) { + this.backBufferContext2D.rotate(angle); + }, + + /** + * scales the canvas context + * @name scale + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x + * @param {Number} y + */ + scale : function (x, y) { + this.backBufferContext2D.scale(x, y); + }, + + /** + * Sets the fill & stroke style colors for the context. + * @name setColor + * @memberOf me.CanvasRenderer + * @function + * @param {me.Color|String} color css color value + */ + setColor : function (color) { + this.backBufferContext2D.strokeStyle = + this.backBufferContext2D.fillStyle = ( + color instanceof me.Color ? + color.toRGBA() : + color + ); + }, + + /** + * Sets the global alpha on the canvas context + * @name setGlobalAlpha + * @memberOf me.CanvasRenderer + * @function + * @param {Number} alpha 0.0 to 1.0 values accepted. + */ + setGlobalAlpha : function (a) { + this.backBufferContext2D.globalAlpha = this.currentColor.glArray[3] = a; + }, + + /** + * sets the line width on the context + * @name setLineWidth + * @memberOf me.CanvasRenderer + * @function + * @param {Number} width Line width + */ + setLineWidth : function (width) { + this.backBufferContext2D.lineWidth = width; + }, + + /** + * Stroke an arc at the specified coordinates with given radius, start and end points + * @name strokeArc + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x arc center point x-axis + * @param {Number} y arc center point y-axis + * @param {Number} radius + * @param {Number} start start angle in radians + * @param {Number} end end angle in radians + * @param {Boolean} [antiClockwise=false] draw arc anti-clockwise + */ + strokeArc : function (x, y, radius, start, end, antiClockwise) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + this.translate(x + radius, y + radius); + this.backBufferContext2D.beginPath(); + this.backBufferContext2D.arc(0, 0, radius, start, end, antiClockwise || false); + this.backBufferContext2D.stroke(); + this.backBufferContext2D.closePath(); + this.translate(-(x + radius), -(y + radius)); + }, + + /** + * Stroke an ellipse at the specified coordinates with given radius, start and end points + * @name strokeEllipse + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x arc center point x-axis + * @param {Number} y arc center point y-axis + * @param {Number} w horizontal radius of the ellipse + * @param {Number} h vertical radius of the ellipse + */ + strokeEllipse : function (x, y, w, h) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + + var hw = w, + hh = h, + lx = x - hw, + rx = x + hw, + ty = y - hh, + by = y + hh; + + var xmagic = hw * 0.551784, + ymagic = hh * 0.551784, + xmin = x - xmagic, + xmax = x + xmagic, + ymin = y - ymagic, + ymax = y + ymagic; + + this.backBufferContext2D.beginPath(); + this.backBufferContext2D.moveTo(x, ty); + this.backBufferContext2D.bezierCurveTo(xmax, ty, rx, ymin, rx, y); + this.backBufferContext2D.bezierCurveTo(rx, ymax, xmax, by, x, by); + this.backBufferContext2D.bezierCurveTo(xmin, by, lx, ymax, lx, y); + this.backBufferContext2D.bezierCurveTo(lx, ymin, xmin, ty, x, ty); + this.backBufferContext2D.stroke(); + }, + + /** + * Stroke a line of the given two points + * @name strokeLine + * @memberOf me.CanvasRenderer + * @function + * @param {Number} startX the start x coordinate + * @param {Number} startY the start y coordinate + * @param {Number} endX the end x coordinate + * @param {Number} endY the end y coordinate + */ + strokeLine : function (startX, startY, endX, endY) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + + this.backBufferContext2D.beginPath(); + this.backBufferContext2D.moveTo(startX, startY); + this.backBufferContext2D.lineTo(endX, endY); + this.backBufferContext2D.stroke(); + }, + + /** + * Strokes a me.Polygon on the screen with a specified color + * @name strokePolygon + * @memberOf me.CanvasRenderer + * @function + * @param {me.Polygon} poly the shape to draw + */ + strokePolygon : function (poly) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + + this.translate(poly.pos.x, poly.pos.y); + this.backBufferContext2D.beginPath(); + this.backBufferContext2D.moveTo(poly.points[0].x, poly.points[0].y); + var point; + for (var i = 1; i < poly.points.length; i++) { + point = poly.points[i]; + this.backBufferContext2D.lineTo(point.x, point.y); + } + this.backBufferContext2D.lineTo(poly.points[0].x, poly.points[0].y); + this.backBufferContext2D.stroke(); + this.backBufferContext2D.closePath(); + this.translate(-poly.pos.x, -poly.pos.y); + }, + + /** + * Stroke a rectangle at the specified coordinates with a given color + * @name strokeRect + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + strokeRect : function (x, y, width, height) { + if (this.backBufferContext2D.globalAlpha < 1 / 255) { + // Fast path: don't draw fully transparent + return; + } + this.backBufferContext2D.strokeRect(x, y, width, height); + }, + + /** + * draw the given shape + * @name drawShape + * @memberOf me.CanvasRenderer + * @function + * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object + */ + drawShape : function (shape) { + if (shape.shapeType === "Rectangle") { + this.strokeRect(shape.left, shape.top, shape.width, shape.height); + } else if (shape instanceof me.Line || shape instanceof me.Polygon) { + this.strokePolygon(shape); + } else if (shape instanceof me.Ellipse) { + if (shape.radiusV.x === shape.radiusV.y) { + // it's a circle + this.strokeArc( + shape.pos.x - shape.radius, + shape.pos.y - shape.radius, + shape.radius, + 0, + 2 * Math.PI + ); + } else { + // it's an ellipse + this.strokeEllipse( + shape.pos.x, + shape.pos.y, + shape.radiusV.x, + shape.radiusV.y + ); + } + } + }, + + /** + * Resets (overrides) the renderer transformation matrix to the + * identity one, and then apply the given transformation matrix. + * @name setTransform + * @memberOf me.CanvasRenderer + * @function + * @param {me.Matrix2d} mat2d Matrix to transform by + */ + setTransform : function (mat2d) { + this.resetTransform(); + this.transform(mat2d); + }, + + /** + * Multiply given matrix into the renderer tranformation matrix + * @name transform + * @memberOf me.CanvasRenderer + * @function + * @param {me.Matrix2d} mat2d Matrix to transform by + */ + transform : function (mat2d) { + var a = mat2d.val; + var tx = a[6], + ty = a[7]; + + if (this.subPixel === false) { + tx = ~~tx; + ty = ~~ty; + } + + this.backBufferContext2D.transform( + a[0], + a[1], + a[3], + a[4], + tx, + ty + ); + }, + + /** + * Translates the context to the given position + * @name translate + * @memberOf me.CanvasRenderer + * @function + * @param {Number} x + * @param {Number} y + */ + translate : function (x, y) { + if (this.subPixel === false) { + this.backBufferContext2D.translate(~~x, ~~y); + } else { + this.backBufferContext2D.translate(x, y); + } + } + + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a local constant for the -(Math.PI / 2) value + * @ignore + */ + var nhPI = -(Math.PI / 2); + + /** + * A Texture atlas object
+ * For portability, a global reference to this class is available through the default renderer: {@link me.video.renderer}.Texture
+ *
+ * Currently supports :
+ * - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export
+ * - [ShoeBox]{@link http://renderhjs.net/shoebox/} : through JSON export using the + * melonJS setting [file]{@link https://github.com/melonjs/melonJS/raw/master/media/shoebox_JSON_export.sbx}
+ * - Standard (fixed cell size) spritesheet : through a {framewidth:xx, frameheight:xx, anchorPoint:me.Vector2d} object + * @class + * @extends me.Object + * @memberOf me.CanvasRenderer + * @name Texture + * @constructor + * @param {Object} atlas atlas information. See {@link me.loader.getJSON} + * @param {Image} [texture=atlas.meta.image] texture name + * @param {Boolean} [cached=false] Use true to skip caching this Texture + * @example + * // create a texture atlas from a JSON Object + * texture = new me.video.renderer.Texture( + * me.loader.getJSON("texture"), + * me.loader.getImage("texture") + * ); + * + * // create a texture atlas for a spritesheet, with (optional) an anchorPoint in the center of each frame + * texture = new me.video.renderer.Texture( + * { framewidth : 32, frameheight : 32, anchorPoint : new me.Vector2d(0.5, 0.5) }, + * me.loader.getImage("spritesheet") + * ); + */ + me.CanvasRenderer.prototype.Texture = me.Object.extend( + /** @scope me.video.renderer.Texture.prototype */ + { + /** + * @ignore + */ + init : function (atlas, texture, cache) { + /** + * to identify the atlas format (e.g. texture packer) + * @ignore + */ + this.format = null; + + /** + * the image texture itself (FIXME: This should be named `image`) + * @ignore + */ + this.texture = texture || null; + + /** + * the atlas dictionnary + * @ignore + */ + this.atlas = null; + + if (typeof (atlas) !== "undefined") { + + if (typeof(atlas.meta) !== "undefined") { + // Texture Packer + if (atlas.meta.app.includes("texturepacker")) { + this.format = "texturepacker"; + // set the texture + if (typeof(texture) === "undefined") { + var image = atlas.meta.image; + this.texture = me.utils.getImage(image); + if (!this.texture) { + throw new me.video.renderer.Texture.Error( + "Atlas texture '" + image + "' not found" + ); + } + } + this.repeat = "no-repeat"; + } + // ShoeBox + else if (atlas.meta.app.includes("ShoeBox")) { + if (!atlas.meta.exporter || !atlas.meta.exporter.includes("melonJS")) { + throw new me.video.renderer.Texture.Error( + "ShoeBox requires the JSON exporter : " + + "https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx" + ); + } + this.format = "ShoeBox"; + this.repeat = "no-repeat"; + } + // Internal texture atlas + else if (atlas.meta.app.includes("melonJS")) { + this.format = "melonJS"; + this.repeat = atlas.meta.repeat || "no-repeat"; + } + // initialize the atlas + this.atlas = this.parse(atlas); + + } else { + // a regular spritesheet ? + if (typeof(atlas.framewidth) !== "undefined" && + typeof(atlas.frameheight) !== "undefined") { + this.format = "Spritesheet (fixed cell size)"; + if (typeof(texture) !== undefined) { + // overwrite if specified + atlas.image = texture; + } + // initialize the atlas + this.atlas = this.parseFromSpriteSheet(atlas); + this.repeat = "no-repeat"; + } + } + } + // if format not recognized + if (!this.atlas) { + throw new me.video.renderer.Texture.Error("texture atlas format not supported"); + } + + // Add self to TextureCache if cache !== false + if (cache !== false) { + if (cache instanceof me.Renderer.TextureCache) { + cache.put(this.texture, this); + } else { + me.video.renderer.cache.put(this.texture, this); + } + } + }, + + /** + * create a simple 1 frame texture atlas based on the given parameters + * @ignore + */ + createAtlas : function (width, height, name, repeat) { + return { + "meta" : { + "app" : "melonJS", + "size" : { "w" : width, "h" : height }, + "repeat" : repeat || "no-repeat" + }, + "frames" : [{ + "filename" : name || "default", + "frame" : { "x" : 0, "y" : 0, "w" : width, "h" : height } + }] + }; + }, + + /** + * build an atlas from the given data + * @ignore + */ + parse : function (data) { + var atlas = {}; + data.frames.forEach(function (frame) { + // fix wrongly formatted JSON (e.g. last dummy object in ShoeBox) + if (frame.hasOwnProperty("filename")) { + // Source coordinates + var s = frame.frame; + + var originX, originY; + // Pixel-based offset origin from the top-left of the source frame + var hasTextureAnchorPoint = (frame.spriteSourceSize && frame.sourceSize && frame.pivot); + if (hasTextureAnchorPoint) { + originX = (frame.sourceSize.w * frame.pivot.x) - ((frame.trimmed) ? frame.spriteSourceSize.x : 0); + originY = (frame.sourceSize.h * frame.pivot.y) - ((frame.trimmed) ? frame.spriteSourceSize.y : 0); + } + + atlas[frame.filename] = { + name : frame.filename, // frame name + offset : new me.Vector2d(s.x, s.y), + anchorPoint : (hasTextureAnchorPoint) ? new me.Vector2d(originX / s.w, originY / s.h) : null, + width : s.w, + height : s.h, + angle : (frame.rotated === true) ? nhPI : 0 + }; + } + }); + return atlas; + }, + + /** + * build an atlas from the given spritesheet + * @ignore + */ + parseFromSpriteSheet : function (data) { + var atlas = {}; + var image = data.image; + var spacing = data.spacing || 0; + var margin = data.margin || 0; + + var width = image.width; + var height = image.height; + + // calculate the sprite count (line, col) + var spritecount = new me.Vector2d( + ~~((width - margin + spacing) / (data.framewidth + spacing)), + ~~((height - margin + spacing) / (data.frameheight + spacing)) + ); + + // verifying the texture size + if ((width % (data.framewidth + spacing)) !== 0 || + (height % (data.frameheight + spacing)) !== 0) { + // "truncate size" + width = spritecount.x * (data.framewidth + spacing); + height = spritecount.y * (data.frameheight + spacing); + // warning message + console.warn( + "Spritesheet Texture for image: " + image.src + + " is not divisible by " + (data.framewidth + spacing) + + "x" + (data.frameheight + spacing) + + ", truncating effective size to " + width + "x" + height + ); + } + + // build the local atlas + for (var frame = 0, count = spritecount.x * spritecount.y; frame < count; frame++) { + atlas["" + frame] = { + name: "" + frame, + offset: new me.Vector2d( + margin + (spacing + data.framewidth) * (frame % spritecount.x), + margin + (spacing + data.frameheight) * ~~(frame / spritecount.x) + ), + anchorPoint: (data.anchorPoint || null), + width: data.framewidth, + height: data.frameheight, + angle: 0 + }; + } + + return atlas; + }, + + /** + * return the Atlas dictionnary + * @name getAtlas + * @memberOf me.CanvasRenderer.Texture + * @function + * @return {Object} + */ + getAtlas : function () { + return this.atlas; + }, + + /** + * return the Atlas texture + * @name getTexture + * @memberOf me.CanvasRenderer.Texture + * @function + * @return {Image} + */ + getTexture : function () { + return this.texture; + }, + + /** + * return a normalized region/frame information for the specified sprite name + * @name getRegion + * @memberOf me.CanvasRenderer.Texture + * @function + * @param {String} name name of the sprite + * @return {Object} + */ + getRegion : function (name) { + return this.atlas[name]; + }, + + /** + * Create a sprite object using the first region found using the specified name + * @name createSpriteFromName + * @memberOf me.CanvasRenderer.Texture + * @function + * @param {String} name name of the sprite + * @param {Object} [settings] Additional settings passed to the {@link me.Sprite} contructor + * @return {me.Sprite} + * @example + * // create a new texture atlas object under the `game` namespace + * game.texture = new me.video.renderer.Texture( + * me.loader.getJSON("texture"), + * me.loader.getImage("texture") + * ); + * ... + * ... + * // add the coin sprite as renderable for the entity + * this.renderable = game.texture.createSpriteFromName("coin.png"); + * // set the renderable position to bottom center + * this.anchorPoint.set(0.5, 1.0); + */ + createSpriteFromName : function (name, settings) { + // instantiate a new sprite object + return me.pool.pull( + "me.Sprite", + 0, 0, + Object.assign({ + image: this, + region : name + }, settings || {}) + ); + }, + + /** + * Create an animation object using the first region found using all specified names + * @name createAnimationFromName + * @memberOf me.CanvasRenderer.Texture + * @function + * @param {String[]|Number[]} names list of names for each sprite + * (when manually creating a Texture out of a spritesheet, only numeric values are authorized) + * @param {Object} [settings] Additional settings passed to the {@link me.Sprite} contructor + * @return {me.Sprite} + * @example + * // create a new texture atlas object under the `game` namespace + * game.texture = new me.video.renderer.Texture( + * me.loader.getJSON("texture"), + * me.loader.getImage("texture") + * ); + * + * // create a new Sprite as renderable for the entity + * this.renderable = game.texture.createAnimationFromName([ + * "walk0001.png", "walk0002.png", "walk0003.png", + * "walk0004.png", "walk0005.png", "walk0006.png", + * "walk0007.png", "walk0008.png", "walk0009.png", + * "walk0010.png", "walk0011.png" + * ]); + * + * // define an additional basic walking animation + * this.renderable.addAnimation ("simple_walk", [0,2,1]); + * // you can also use frame name to define your animation + * this.renderable.addAnimation ("speed_walk", ["walk0007.png", "walk0008.png", "walk0009.png", "walk0010.png"]); + * // set the default animation + * this.renderable.setCurrentAnimation("simple_walk"); + * // set the renderable position to bottom center + * this.anchorPoint.set(0.5, 1.0); + */ + createAnimationFromName : function (names, settings) { + var tpAtlas = [], indices = {}; + var width = 0, height = 0; + var region; + // iterate through the given names + // and create a "normalized" atlas + for (var i = 0; i < names.length; ++i) { + region = this.getRegion(names[i]); + if (region == null) { + // throw an error + throw new me.video.renderer.Texture.Error("Texture - region for " + names[i] + " not found"); + } + tpAtlas[i] = region; + // save the corresponding index + indices[names[i]] = i; + // calculate the max size of a frame + width = Math.max(region.width, width); + height = Math.max(region.height, height); + } + // instantiate a new animation sheet object + return new me.Sprite(0, 0, Object.assign({ + image: this, + framewidth: width, + frameheight: height, + margin: 0, + spacing: 0, + atlas: tpAtlas, + atlasIndices: indices + }, settings || {})); + } + }); + + /** + * Base class for Texture exception handling. + * @name Error + * @class + * @memberOf me.CanvasRenderer.Texture + * @constructor + * @param {String} msg Error message. + */ + me.CanvasRenderer.prototype.Texture.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.CanvasRenderer.Texture.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016 Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * The WebGL Shader singleton
+ * There is no constructor function for me.video.shader + * @namespace me.video.shader + * @memberOf me.video + */ + me.video.shader = (function () { + /** + * Public API + * @ignore + */ + var api = {}; + + /** + * Compile GLSL into a shader object + * @private + */ + function getShader(gl, type, source) { + var shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + throw new me.video.Error(gl.getShaderInfoLog(shader)); + } + + return shader; + } + + /** + * Hash map of GLSL data types to WebGL Uniform methods + * @private + */ + var fnHash = { + "bool" : "1i", + "int" : "1i", + "float" : "1f", + "vec2" : "2fv", + "vec3" : "3fv", + "vec4" : "4fv", + "bvec2" : "2iv", + "bvec3" : "3iv", + "bvec4" : "4iv", + "ivec2" : "2iv", + "ivec3" : "3iv", + "ivec4" : "4iv", + "mat2" : "Matrix2fv", + "mat3" : "Matrix3fv", + "mat4" : "Matrix4fv", + "sampler2D" : "1i" + }; + + /** + * Create a shader program (with bindings) using the given GLSL sources + * @name createShader + * @memberOf me.video.shader + * @function + * @param {WebGLContext} gl WebGL Context + * @param {String} vertex Vertex shader source + * @param {String} fragment Fragment shader source + * @return {Object} A reference to the WebGL Shader Program + */ + api.createShader = function (gl, vertex, fragment) { + var program = { + "attributes" : {}, + "uniforms" : {}, + "handle" : null + }, + handle = program.handle = gl.createProgram(), + attrRx = /attribute\s+\w+\s+(\w+)/g, + uniRx = /uniform\s+(\w+)\s+(\w+)/g, + attributes = [], + uniforms = {}, + match, + descriptor = {}, + locations = {}; + + gl.attachShader(handle, getShader(gl, gl.VERTEX_SHADER, vertex)); + gl.attachShader(handle, getShader(gl, gl.FRAGMENT_SHADER, fragment)); + gl.linkProgram(handle); + + if (!gl.getProgramParameter(handle, gl.LINK_STATUS)) { + throw new me.video.Error(gl.getProgramInfoLog(handle)); + } + + gl.useProgram(handle); + + // Detect all attribute names + while ((match = attrRx.exec(vertex))) { + attributes.push(match[1]); + } + + // Detect all uniform names and types + [ vertex, fragment ].forEach(function (shader) { + while ((match = uniRx.exec(shader))) { + uniforms[match[2]] = match[1]; + } + }); + + // Get attribute references + attributes.forEach(function (attr) { + program.attributes[attr] = gl.getAttribLocation(handle, attr); + gl.enableVertexAttribArray(program.attributes[attr]); + }); + + // Get uniform references + Object.keys(uniforms).forEach(function (name) { + var type = uniforms[name]; + locations[name] = gl.getUniformLocation(handle, name); + + descriptor[name] = { + "get" : (function (name) { + /** + * A getter for the uniform location + * @ignore + */ + return function () { + return locations[name]; + }; + })(name), + "set" : (function (name, type, fn) { + if (type.indexOf("mat") === 0) { + /** + * A generic setter for uniform matrices + * @ignore + */ + return function (val) { + gl[fn](locations[name], false, val); + }; + } + else { + /** + * A generic setter for uniform vectors + * @ignore + */ + return function (val) { + var fnv = fn; + if (val.length && fn.substr(-1) !== "v") { + fnv += "v"; + } + gl[fnv](locations[name], val); + }; + } + })(name, type, "uniform" + fnHash[type]) + }; + }); + Object.defineProperties(program.uniforms, descriptor); + + return program; + }; + + /** + * Create a texture from an image + * @name createTexture + * @memberOf me.video.shader + * @function + * @param {WebGLContext} gl WebGL Context + * @param {Number} unit Destination texture unit + * @param {Image|Canvas|ImageData|UInt8Array[]|Float32Array[]} image Source image + * @param {Number} filter gl.LINEAR or gl.NEAREST + * @param {String} [repeat="no-repeat"] Image repeat behavior (see {@link me.ImageLayer#repeat}) + * @param {Number} [w] Source image width (Only use with UInt8Array[] or Float32Array[] source image) + * @param {Number} [h] Source image height (Only use with UInt8Array[] or Float32Array[] source image) + * @param {Number} [b] Source image border (Only use with UInt8Array[] or Float32Array[] source image) + * @return {WebGLTexture} A texture object + */ + api.createTexture = function (gl, unit, image, filter, repeat, w, h, b) { + repeat = repeat || "no-repeat"; + + if (!me.utils.isPowerOfTwo(w || image.width) || !me.utils.isPowerOfTwo(h || image.height)) { + console.warn( + "[WebGL Renderer] " + image + " is not a POT texture " + + "(" + (w || image.width) + "x" + (h || image.height) + ")" + ); + } + + var texture = gl.createTexture(), + rs = (repeat.search(/^repeat(-x)?$/) === 0) ? gl.REPEAT : gl.CLAMP_TO_EDGE, + rt = (repeat.search(/^repeat(-y)?$/) === 0) ? gl.REPEAT : gl.CLAMP_TO_EDGE; + + gl.activeTexture(gl.TEXTURE0 + unit); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, rs); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, rt); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); + if (w || h || b) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, b, gl.RGBA, gl.UNSIGNED_BYTE, image); + } + else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + } + + return texture; + }; + + return api; + })(); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016 Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * a WebGL renderer object + * @extends me.Renderer + * @namespace me.WebGLRenderer + * @memberOf me + * @constructor + * @param {Canvas} canvas The html canvas tag to draw to on screen. + * @param {Number} width The width of the canvas without scaling + * @param {Number} height The height of the canvas without scaling + * @param {Object} [options] The renderer parameters + * @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering + * @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing + * @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled) + * @param {Boolean} [options.subPixel=false] Whether to enable subpixel renderering (performance hit when enabled) + * @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied + * @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied + * @param {me.WebGLRenderer.Compositor} [options.compositor] A class that implements the compositor API + */ + me.WebGLRenderer = me.Renderer.extend( + /** @scope me.WebGLRenderer.prototype */ + { + /** + * @ignore + */ + init : function (c, width, height, options) { + me.Renderer.prototype.init.apply(this, [c, width, height, options]); + + /** + * The WebGL context + * @name gl + * @memberOf me.WebGLRenderer + */ + this.gl = this.getContextGL(c, !this.transparent); + + /** + * @ignore + */ + this.colorStack = []; + + /** + * @ignore + */ + this._matrixStack = []; + + /** + * @ignore + */ + this._linePoints = [ + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d() + ]; + + /** + * The current transformation matrix used for transformations on the overall scene + * @name currentTransform + * @type me.Matrix2d + * @memberOf me.WebGLRenderer + */ + this.currentTransform = new me.Matrix2d(); + + // Create a compositor + var Compositor = options.compositor || me.WebGLRenderer.Compositor; + this.compositor = new Compositor(this); + + // Create a texture cache + this.cache = new me.Renderer.TextureCache( + this.compositor.maxTextures + ); + + this.createFillTexture(this.cache); + + // Configure the WebGL viewport + this.scaleCanvas(1, 1); + + return this; + }, + + /** + * @ignore + */ + createFillTexture : function (cache) { + // Create a 1x1 white texture for fill operations + var image = new Uint8Array([255, 255, 255, 255]); + + /** + * @ignore + */ + this.fillTexture = new this.Texture( + this.Texture.prototype.createAtlas.apply( + this.Texture.prototype, + [ 1, 1, "fillTexture"] + ), + image, + cache + ); + + this.compositor.uploadTexture( + this.fillTexture, + 1, + 1, + 0 + ); + }, + + /** + * @ignore + */ + createFontTexture : function (cache) { + var image = me.video.createCanvas( + me.utils.nextPowerOfTwo(this.backBufferCanvas.width), + me.utils.nextPowerOfTwo(this.backBufferCanvas.height) + ); + + /** + * @ignore + */ + this.fontContext2D = this.getContext2d(image); + + /** + * @ignore + */ + this.fontTexture = new this.Texture( + this.Texture.prototype.createAtlas.apply( + this.Texture.prototype, + [ this.backBufferCanvas.width, this.backBufferCanvas.height, "fontTexture"] + ), + image, + cache + ); + + this.compositor.uploadTexture(this.fontTexture); + }, + + /** + * Create a pattern with the specified repetition + * @name createPattern + * @memberOf me.WebGLRenderer + * @function + * @param {image} image Source image + * @param {String} repeat Define how the pattern should be repeated + * @return {me.video.renderer.Texture} + * @see me.ImageLayer#repeat + * @example + * var tileable = renderer.createPattern(image, "repeat"); + * var horizontal = renderer.createPattern(image, "repeat-x"); + * var vertical = renderer.createPattern(image, "repeat-y"); + * var basic = renderer.createPattern(image, "no-repeat"); + */ + createPattern : function (image, repeat) { + + if (!me.utils.isPowerOfTwo(image.width) || !me.utils.isPowerOfTwo(image.height)) { + throw new me.video.Error( + "[WebGL Renderer] " + image + " is not a POT texture " + + "(" + image.width + "x" + image.height + ")" + ); + } + + var texture = new this.Texture( + this.Texture.prototype.createAtlas.apply( + this.Texture.prototype, + [ image.width, image.height, "pattern", repeat] + ), + image + ); + + // FIXME: Remove old cache entry and texture when changing the repeat mode + this.compositor.uploadTexture(texture); + + return texture; + }, + + /** + * Flush the compositor to the frame buffer + * @name flush + * @memberOf me.WebGLRenderer + * @function + */ + flush : function () { + this.compositor.flush(); + }, + + /** + * Clears the gl context with the given color. + * @name clearColor + * @memberOf me.WebGLRenderer + * @function + * @param {me.Color|String} color CSS color. + * @param {Boolean} [opaque=false] Allow transparency [default] or clear the surface completely [true] + */ + clearColor : function (col, opaque) { + var color = this.currentColor.clone(); + var matrix = this.currentTransform.clone(); + this.currentColor.copy(col); + this.currentTransform.identity(); + + if (opaque) { + this.compositor.clear(); + } + else { + this.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + this.currentTransform.copy(matrix); + this.currentColor.copy(color); + me.pool.push(color); + }, + + /** + * Sets all pixels in the given rectangle to transparent black,
+ * erasing any previously drawn content. + * @name clearRect + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x x axis of the coordinate for the rectangle starting point. + * @param {Number} y y axis of the coordinate for the rectangle starting point. + * @param {Number} width The rectangle's width. + * @param {Number} height The rectangle's height. + */ + clearRect : function (x, y, width, height) { + var color = this.currentColor.clone(); + this.currentColor.copy("#0000"); + this.fillRect(x, y, width, height); + this.currentColor.copy(color); + me.pool.push(color); + }, + + /** + * @ignore + */ + drawFont : function (bounds) { + var fontContext = this.getFontContext(); + + // Flush the compositor so we can upload a new texture + this.compositor.flush(); + + // Force-upload the new texture + this.compositor.uploadTexture(this.fontTexture, 0, 0, 0, true); + + // Add the new quad + var key = bounds.pos.x + "," + bounds.pos.y + "," + bounds.width + "," + bounds.height; + this.compositor.addQuad( + this.fontTexture, + key, + bounds.pos.x, + bounds.pos.y, + bounds.width, + bounds.height + ); + + // Clear font context2D + fontContext.clearRect(0, 0, this.backBufferCanvas.width, this.backBufferCanvas.height); + }, + + /** + * Draw an image to the gl context + * @name drawImage + * @memberOf me.WebGLRenderer + * @function + * @param {Image} image Source image + * @param {Number} sx Source x-coordinate + * @param {Number} sy Source y-coordinate + * @param {Number} sw Source width + * @param {Number} sh Source height + * @param {Number} dx Destination x-coordinate + * @param {Number} dy Destination y-coordinate + * @param {Number} dw Destination width + * @param {Number} dh Destination height + * @example + * // Can be used in three ways: + * renderer.drawImage(image, dx, dy); + * renderer.drawImage(image, dx, dy, dw, dh); + * renderer.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh); + * // dx, dy, dw, dh being the destination target & dimensions. sx, sy, sw, sh being the position & dimensions to take from the image + */ + drawImage : function (image, sx, sy, sw, sh, dx, dy, dw, dh) { + // TODO: Replace the function signature with: + // drawImage(Image|Object, sx, sy, sw, sh, dx, dy, dw, dh) + if (typeof sw === "undefined") { + sw = dw = image.width; + sh = dh = image.height; + dx = sx; + dy = sy; + sx = 0; + sy = 0; + } + else if (typeof dx === "undefined") { + dx = sx; + dy = sy; + dw = sw; + dh = sh; + sw = image.width; + sh = image.height; + sx = 0; + sy = 0; + } + + if (this.subPixel === false) { + // clamp to pixel grid + dx = ~~dx; + dy = ~~dy; + } + + var key = sx + "," + sy + "," + sw + "," + sh; + this.compositor.addQuad(this.cache.get(image), key, dx, dy, dw, dh); + }, + + /** + * Draw a pattern within the given rectangle. + * @name drawPattern + * @memberOf me.WebGLRenderer + * @function + * @param {me.video.renderer.Texture} pattern Pattern object + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + * @see me.WebGLRenderer#createPattern + */ + drawPattern : function (pattern, x, y, width, height) { + var key = "0,0," + width + "," + height; + this.compositor.addQuad(pattern, key, x, y, width, height); + }, + + /** + * Draw a filled rectangle at the specified coordinates + * @name fillRect + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + fillRect : function (x, y, width, height) { + this.compositor.addQuad(this.fillTexture, "default", x, y, width, height); + }, + + /** + * return a reference to the screen canvas corresponding WebGL Context
+ * @name getScreenContext + * @memberOf me.WebGLRenderer + * @function + * @return {WebGLContext} + */ + getScreenContext : function () { + return this.gl; + }, + + /** + * Returns the WebGL Context object of the given Canvas + * @name getContextGL + * @memberOf me.WebGLRenderer + * @function + * @param {Canvas} canvas + * @param {Boolean} [opaque=false] Use true to disable transparency + * @return {WebGLContext} + */ + getContextGL : function (c, opaque) { + if (typeof c === "undefined" || c === null) { + throw new me.video.Error( + "You must pass a canvas element in order to create " + + "a GL context" + ); + } + + if (typeof c.getContext === "undefined") { + throw new me.video.Error( + "Your browser does not support WebGL." + ); + } + + var attr = { + antialias : this.antiAlias, + alpha : !opaque + }; + return ( + c.getContext("webgl", attr) || + c.getContext("experimental-webgl", attr) + ); + }, + + /** + * Returns the WebGLContext instance for the renderer + * return a reference to the system 2d Context + * @name getContext + * @memberOf me.WebGLRenderer + * @function + * @return {WebGLContext} + */ + getContext : function () { + return this.gl; + }, + + /** + * return a reference to the font 2d Context + * @ignore + */ + getFontContext : function () { + if (typeof (this.fontContext2D) === "undefined" ) { + // warn the end user about performance impact + console.warn("[WebGL Renderer] WARNING : Using Standard me.Font with WebGL will severly impact performances !"); + // create the font texture if not done yet + this.createFontTexture(this.cache); + } + return this.fontContext2D; + }, + + /** + * resets the gl transform to identity + * @name resetTransform + * @memberOf me.WebGLRenderer + * @function + */ + resetTransform : function () { + this.currentTransform.identity(); + }, + + /** + * Reset context state + * @name reset + * @memberOf me.WebGLRenderer + * @function + */ + reset : function () { + this.resetTransform(); + this.cache.reset(); + this.compositor.reset(); + this.createFillTexture(); + if (typeof (this.fontContext2D) !== "undefined" ) { + this.createFontTexture(); + } + }, + + /** + * scales the canvas & GL Context + * @name scaleCanvas + * @memberOf me.WebGLRenderer + * @function + */ + scaleCanvas : function (scaleX, scaleY) { + var w = this.canvas.width * scaleX; + var h = this.canvas.height * scaleY; + + // adjust CSS style for High-DPI devices + if (me.device.getPixelRatio() > 1) { + this.canvas.style.width = (w / me.device.getPixelRatio()) + "px"; + this.canvas.style.height = (h / me.device.getPixelRatio()) + "px"; + } + else { + this.canvas.style.width = w + "px"; + this.canvas.style.height = h + "px"; + } + + this.compositor.setProjection(this.canvas.width, this.canvas.height); + }, + + /** + * restores the canvas context + * @name restore + * @memberOf me.WebGLRenderer + * @function + */ + restore : function () { + var color = this.colorStack.pop(); + me.pool.push(color); + this.currentColor.copy(color); + this.currentTransform.copy(this._matrixStack.pop()); + }, + + /** + * rotates the uniform matrix + * @name rotate + * @memberOf me.WebGLRenderer + * @function + * @param {Number} angle in radians + */ + rotate : function (angle) { + this.currentTransform.rotate(angle); + }, + + /** + * save the canvas context + * @name save + * @memberOf me.WebGLRenderer + * @function + */ + save : function () { + this.colorStack.push(this.currentColor.clone()); + this._matrixStack.push(this.currentTransform.clone()); + }, + + /** + * scales the uniform matrix + * @name scale + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x + * @param {Number} y + */ + scale : function (x, y) { + this.currentTransform.scale(x, y); + }, + + /** + * not used by this renderer? + * @ignore + */ + setAntiAlias : function (context, enable) { + me.Renderer.prototype.setAntiAlias.apply(this, [context, enable]); + // TODO: perhaps handle GLNEAREST or other options with texture binding + }, + + /** + * return the current global alpha + * @name globalAlpha + * @memberOf me.WebGLRenderer + * @function + * @return {Number} + */ + setGlobalAlpha : function (a) { + this.currentColor.glArray[3] = a; + }, + + /** + * Sets the color for further draw calls + * @name setColor + * @memberOf me.WebGLRenderer + * @function + * @param {me.Color|String} color css color string. + */ + setColor : function (color) { + var alpha = this.currentColor.glArray[3]; + this.currentColor.copy(color); + this.currentColor.glArray[3] *= alpha; + }, + + /** + * Set the line width + * @name setLineWidth + * @memberOf me.WebGLRenderer + * @function + * @param {Number} width Line width + */ + setLineWidth : function (width) { + this.compositor.lineWidth(width); + }, + + /** + * Stroke an arc at the specified coordinates with given radius, start and end points + * @name strokeArc + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x arc center point x-axis + * @param {Number} y arc center point y-axis + * @param {Number} radius + * @param {Number} start start angle in radians + * @param {Number} end end angle in radians + * @param {Boolean} [antiClockwise=false] draw arc anti-clockwise + */ + strokeArc : function (/*x, y, radius, start, end, antiClockwise*/) { + // TODO + }, + + /** + * Stroke an ellipse at the specified coordinates with given radius, start and end points + * @name strokeEllipse + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x arc center point x-axis + * @param {Number} y arc center point y-axis + * @param {Number} w horizontal radius of the ellipse + * @param {Number} h vertical radius of the ellipse + */ + strokeEllipse : function (/*x, y, w, h*/) { + // TODO + }, + + /** + * Stroke a line of the given two points + * @name strokeLine + * @memberOf me.WebGLRenderer + * @function + * @param {Number} startX the start x coordinate + * @param {Number} startY the start y coordinate + * @param {Number} endX the end x coordinate + * @param {Number} endY the end y coordinate + */ + strokeLine : function (startX, startY, endX, endY) { + var points = this._linePoints.slice(0, 2); + points[0].x = startX; + points[0].y = startY; + points[1].x = endX; + points[1].y = endY; + this.compositor.drawLine(points, true); + }, + + /** + * Strokes a me.Polygon on the screen with a specified color + * @name strokePolygon + * @memberOf me.WebGLRenderer + * @function + * @param {me.Polygon} poly the shape to draw + */ + strokePolygon : function (poly) { + var len = poly.points.length, + points, + i; + + // Grow internal points buffer if necessary + for (i = this._linePoints.length; i < len; i++) { + this._linePoints.push(new me.Vector2d()); + } + + points = this._linePoints.slice(0, len); + for (i = 0; i < len; i++) { + points[i].x = poly.pos.x + poly.points[i].x; + points[i].y = poly.pos.y + poly.points[i].y; + } + this.compositor.drawLine(points); + }, + + /** + * Draw a stroke rectangle at the specified coordinates + * @name strokeRect + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + strokeRect : function (x, y, width, height) { + var points = this._linePoints.slice(0, 4); + points[0].x = x; + points[0].y = y; + points[1].x = x + width; + points[1].y = y; + points[2].x = x + width; + points[2].y = y + height; + points[3].x = x; + points[3].y = y + height; + this.compositor.drawLine(points); + }, + + /** + * draw the given shape + * @name drawShape + * @memberOf me.WebGLRenderer + * @function + * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object + */ + drawShape : function (shape) { + if (shape.shapeType === "Rectangle") { + this.strokeRect(shape.left, shape.top, shape.width, shape.height); + } else if (shape instanceof me.Line || shape instanceof me.Polygon) { + this.strokePolygon(shape); + } else if (shape instanceof me.Ellipse) { + if (shape.radiusV.x === shape.radiusV.y) { + // it's a circle + this.strokeArc( + shape.pos.x - shape.radius, + shape.pos.y - shape.radius, + shape.radius, + 0, + 2 * Math.PI + ); + } else { + // it's an ellipse + this.strokeEllipse( + shape.pos.x, + shape.pos.y, + shape.radiusV.x, + shape.radiusV.y + ); + } + } + }, + + /** + * Resets (overrides) the renderer transformation matrix to the + * identity one, and then apply the given transformation matrix. + * @name setTransform + * @memberOf me.WebGLRenderer + * @function + * @param {me.Matrix2d} mat2d Matrix to transform by + */ + setTransform : function (mat2d) { + this.resetTransform(); + this.transform(mat2d); + }, + + /** + * Multiply given matrix into the renderer tranformation matrix + * @name transform + * @memberOf me.WebGLRenderer + * @function + * @param {me.Matrix2d} mat2d Matrix to transform by + */ + transform : function (mat2d) { + this.currentTransform.multiply(mat2d); + if (this.subPixel === false) { + // snap position values to pixel grid + var a = this.currentTransform.val; + a[6] = ~~a[6]; + a[7] = ~~a[7]; + } + }, + + /** + * Translates the uniform matrix by the given coordinates + * @name translate + * @memberOf me.WebGLRenderer + * @function + * @param {Number} x + * @param {Number} y + */ + translate : function (x, y) { + if (this.subPixel === false) { + this.currentTransform.translate(~~x, ~~y); + } else { + this.currentTransform.translate(x, y); + } + } + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A Texture atlas object for WebGL
+ * For portability, a global reference to this class is available through the default renderer: {@link me.video.renderer}.Texture
+ *
+ * Currently supports :
+ * - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export
+ * - [ShoeBox]{@link http://renderhjs.net/shoebox/} : through JSON export using the + * melonJS setting [file]{@link https://github.com/melonjs/melonJS/raw/master/media/shoebox_JSON_export.sbx}
+ * - Standard (fixed cell size) spritesheet : through a {framewidth:xx, frameheight:xx} object + * @class + * @extends me.CanvasRenderer + * @memberOf me.WebGLRenderer + * @name Texture + * @constructor + * @param {Object} atlas atlas information. See {@link me.loader.getJSON} + * @param {Image} [texture=atlas.meta.image] texture name + * @param {Boolean} [cached=false] Use true to skip caching this Texture + * @example + * // create a texture atlas from a JSON Object + * texture = new me.video.renderer.Texture( + * me.loader.getJSON("texture"), + * me.loader.getImage("texture") + * ); + * + * // create a texture atlas for a spritesheet + * texture = new me.video.renderer.Texture( + * { framewidth : 32, frameheight : 32 }, + * me.loader.getImage("spritesheet") + * ); + */ + me.WebGLRenderer.prototype.Texture = me.CanvasRenderer.prototype.Texture.extend( + /** @scope me.video.renderer.Texture.prototype */ + { + /** + * @ignore + */ + parse : function (data) { + var w = data.meta.size.w; + var h = data.meta.size.h; + var atlas = me.CanvasRenderer.prototype.Texture.prototype.parse.apply(this, [ data ]); + + return this._addStMap(atlas, w, h); + }, + + /** + * @ignore + */ + parseFromSpriteSheet : function (data) { + var w = data.image.width; + var h = data.image.height; + var atlas = me.CanvasRenderer.prototype.Texture.prototype.parseFromSpriteSheet.apply(this, [ data ]); + + return this._addStMap(atlas, w, h); + }, + + /** + * @ignore + */ + _addStMap : function (atlas, w, h) { + Object.keys(atlas).forEach(function (frame) { + // Source coordinates + var s = atlas[frame].offset; + var sw = atlas[frame].width; + var sh = atlas[frame].height; + + // ST texture coordinates + atlas[frame].stMap = new Float32Array([ + s.x / w, // Left + s.y / h, // Top + (s.x + sw) / w, // Right + (s.y + sh) / h // Bottom + ]); + + // Cache source coordinates + // TODO: Remove this when the Batcher only accepts a region name + var key = s.x + "," + s.y + "," + w + "," + h; + atlas[key] = atlas[frame]; + }); + return atlas; + }, + + /** + * @ignore + */ + _insertRegion : function (name, x, y, w, h) { + var dw = this.texture.width; + var dh = this.texture.height; + this.atlas[name] = { + name : name, + offset : new me.Vector2d(x, y), + width : w, + height : h, + angle : 0, + stMap : new Float32Array([ + x / dw, // Left + y / dh, // Top + (x + w) / dw, // Right + (y + h) / dh // Bottom + ]) + }; + + return this.atlas[name]; + } + }); + + /** + * Base class for Texture exception handling. + * @ignore + */ + me.WebGLRenderer.prototype.Texture.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.WebGLRenderer.Texture.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016 Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ +(function () { + + // Handy constants + var VERTEX_SIZE = 2; + var COLOR_SIZE = 4; + var TEXTURE_SIZE = 1; + var REGION_SIZE = 2; + + var ELEMENT_SIZE = VERTEX_SIZE + COLOR_SIZE + TEXTURE_SIZE + REGION_SIZE; + var ELEMENT_OFFSET = ELEMENT_SIZE * Float32Array.BYTES_PER_ELEMENT; + + var VERTEX_ELEMENT = 0; + var COLOR_ELEMENT = VERTEX_ELEMENT + VERTEX_SIZE; + var TEXTURE_ELEMENT = COLOR_ELEMENT + COLOR_SIZE; + var REGION_ELEMENT = TEXTURE_ELEMENT + TEXTURE_SIZE; + + var VERTEX_OFFSET = VERTEX_ELEMENT * Float32Array.BYTES_PER_ELEMENT; + var COLOR_OFFSET = COLOR_ELEMENT * Float32Array.BYTES_PER_ELEMENT; + var TEXTURE_OFFSET = TEXTURE_ELEMENT * Float32Array.BYTES_PER_ELEMENT; + var REGION_OFFSET = REGION_ELEMENT * Float32Array.BYTES_PER_ELEMENT; + + var ELEMENTS_PER_QUAD = 4; + var INDICES_PER_QUAD = 6; + + var MAX_LENGTH = 16000; + + /** + * A WebGL texture Compositor object. This class handles all of the WebGL state
+ * Pushes texture regions into WebGL buffers, automatically flushes to GPU + * @extends me.Object + * @namespace me.WebGLRenderer.Compositor + * @memberOf me + * @constructor + * @param {me.WebGLRenderer} renderer the current WebGL renderer session + */ + me.WebGLRenderer.Compositor = me.Object.extend( + /** @scope me.WebGLRenderer.Compositor.prototype */ + { + /** + * @ignore + */ + init : function (renderer) { + // local reference + var gl = renderer.gl; + + /** + * The number of quads held in the batch + * @name length + * @memberOf me.WebGLRenderer.Compositor + * @type Number + * @readonly + */ + this.length = 0; + + // Hash map of texture units + this.units = []; + /* + * XXX: The GLSL compiler pukes with "memory exhausted" when it is + * given long if-then-else chains. + * + * See: http://stackoverflow.com/questions/15828966/glsl-compile-error-memory-exhausted + * + * Workaround the problem by limiting the max texture support to 24. + * The magic number was determined by testing under different UAs. + * All Desktop UAs were capable of compiling with 27 fragment shader + * samplers. Using 24 seems like a reasonable compromise; + * + * 24 = 2^4 + 2^3 + * + * As of October 2015, approximately 4.2% of all WebGL-enabled UAs + * support more than 24 max textures, according to + * http://webglstats.com/ + */ + this.maxTextures = Math.min(24, gl.getParameter( + gl.MAX_TEXTURE_IMAGE_UNITS + )); + + // Vector pool + this.v = [ + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d() + ]; + + // the associated renderer + // TODO : add a set context or whatever function, and split + // the constructor accordingly, so that this is easier to restore + // the GL context when lost + this.renderer = renderer; + + // WebGL context + this.gl = renderer.gl; + + // Global transformation matrix + this.matrix = renderer.currentTransform; + + // Global color + this.color = renderer.currentColor; + + // Uniform projection matrix + this.uMatrix = new me.Matrix2d(); + + // Detect GPU capabilities + var precision = (gl.getShaderPrecisionFormat( + gl.FRAGMENT_SHADER, + gl.HIGH_FLOAT + ).precision < 16) ? "mediump" : "highp"; + + // Load and create shader programs + /* eslint-disable */ + this.lineShader = me.video.shader.createShader( + this.gl, + (function anonymous(ctx){var out='precision highp float;attribute vec2 aVertex;uniform mat3 uMatrix;void main(void){gl_Position=vec4((uMatrix*vec3(aVertex,1)).xy,0,1);}';return out;})(), + (function anonymous(ctx){var out='precision '+(ctx.precision)+' float;uniform vec4 uColor;void main(void){gl_FragColor=uColor;}';return out;})({ + "precision" : precision + }) + ); + this.quadShader = me.video.shader.createShader( + this.gl, + (function anonymous(ctx){var out='precision highp float;attribute vec2 aVertex;attribute vec4 aColor;attribute float aTexture;attribute vec2 aRegion;uniform mat3 uMatrix;varying vec4 vColor;varying float vTexture;varying vec2 vRegion;void main(void){gl_Position=vec4((uMatrix*vec3(aVertex,1)).xy,0,1);vColor=vec4(aColor.rgb*aColor.a,aColor.a);vTexture=aTexture;vRegion=aRegion;}';return out;})(), + (function anonymous(ctx){var out='precision '+(ctx.precision)+' float;uniform sampler2D uSampler['+(ctx.maxTextures)+'];varying vec4 vColor;varying float vTexture;varying vec2 vRegion;void main(void){int texture=int(vTexture);if(texture==0){gl_FragColor=texture2D(uSampler[0],vRegion)*vColor;}';for(var i=1;i= MAX_LENGTH) { + this.flush(); + } + if (this.length >= this.sbSize) { + this.resizeSB(); + } + + // Transform vertices + var m = this.matrix, + v0 = this.v[0].set(x, y), + v1 = this.v[1].set(x + w, y), + v2 = this.v[2].set(x, y + h), + v3 = this.v[3].set(x + w, y + h); + + if (!m.isIdentity()) { + m.multiplyVector(v0); + m.multiplyVector(v1); + m.multiplyVector(v2); + m.multiplyVector(v3); + } + + // Array index computation + var idx0 = this.sbIndex, + idx1 = idx0 + ELEMENT_SIZE, + idx2 = idx1 + ELEMENT_SIZE, + idx3 = idx2 + ELEMENT_SIZE; + + // Fill vertex buffer + // FIXME: Pack each vertex vector into single float + this.stream[idx0 + VERTEX_ELEMENT + 0] = v0.x; + this.stream[idx0 + VERTEX_ELEMENT + 1] = v0.y; + this.stream[idx1 + VERTEX_ELEMENT + 0] = v1.x; + this.stream[idx1 + VERTEX_ELEMENT + 1] = v1.y; + this.stream[idx2 + VERTEX_ELEMENT + 0] = v2.x; + this.stream[idx2 + VERTEX_ELEMENT + 1] = v2.y; + this.stream[idx3 + VERTEX_ELEMENT + 0] = v3.x; + this.stream[idx3 + VERTEX_ELEMENT + 1] = v3.y; + + // Fill color buffer + // FIXME: Pack color vector into single float + this.stream.set(color, idx0 + COLOR_ELEMENT); + this.stream.set(color, idx1 + COLOR_ELEMENT); + this.stream.set(color, idx2 + COLOR_ELEMENT); + this.stream.set(color, idx3 + COLOR_ELEMENT); + + // Fill texture index buffer + // FIXME: Can the texture index be packed into another element? + var unit = this.uploadTexture(texture); + this.stream[idx0 + TEXTURE_ELEMENT] = + this.stream[idx1 + TEXTURE_ELEMENT] = + this.stream[idx2 + TEXTURE_ELEMENT] = + this.stream[idx3 + TEXTURE_ELEMENT] = unit; + + // Get the source texture region + var region = texture.getRegion(key); + if (typeof(region) === "undefined") { + // TODO: Require proper atlas regions instead of caching arbitrary region keys + if (me.video.renderer.verbose === true) { + console.warn("Adding texture region", key, "for texture", texture); + } + + var keys = key.split(","), + sx = +keys[0], + sy = +keys[1], + sw = +keys[2], + sh = +keys[3]; + region = texture._insertRegion(key, sx, sy, sw, sh); + } + + // Fill texture coordinates buffer + // FIXME: Pack each texture coordinate into single floats + var stMap = region.stMap; + this.stream[idx0 + REGION_ELEMENT + 0] = stMap[0]; + this.stream[idx0 + REGION_ELEMENT + 1] = stMap[1]; + this.stream[idx1 + REGION_ELEMENT + 0] = stMap[2]; + this.stream[idx1 + REGION_ELEMENT + 1] = stMap[1]; + this.stream[idx2 + REGION_ELEMENT + 0] = stMap[0]; + this.stream[idx2 + REGION_ELEMENT + 1] = stMap[3]; + this.stream[idx3 + REGION_ELEMENT + 0] = stMap[2]; + this.stream[idx3 + REGION_ELEMENT + 1] = stMap[3]; + + this.sbIndex += ELEMENT_SIZE * ELEMENTS_PER_QUAD; + this.length++; + }, + + /** + * Flush batched texture operations to the GPU + * @name flush + * @memberOf me.WebGLRenderer.Compositor + * @function + */ + flush : function () { + if (this.length) { + var gl = this.gl; + + // Copy data into stream buffer + var len = this.length * ELEMENT_SIZE * ELEMENTS_PER_QUAD; + gl.bufferData( + gl.ARRAY_BUFFER, + this.stream.subarray(0, len), + gl.STREAM_DRAW + ); + + // Draw the stream buffer + gl.drawElements( + gl.TRIANGLES, + this.length * INDICES_PER_QUAD, + gl.UNSIGNED_SHORT, + 0 + ); + + this.sbIndex = 0; + this.length = 0; + } + }, + + /** + * Draw a line + * @name drawLine + * @memberOf me.WebGLRenderer.Compositor + * @function + * @param {me.Vector2d[]} points Line vertices + * @param {Boolean} [open=false] Whether the line is open (true) or closed (false) + */ + drawLine : function (points, open) { + this.useShader(this.lineShader.handle); + + // Put vertex data into the stream buffer + var j = 0; + for (var i = 0; i < points.length; i++) { + if (!this.matrix.isIdentity()) { + this.matrix.multiplyVector(points[i]); + } + this.stream[j++] = points[i].x; + this.stream[j++] = points[i].y; + } + + var gl = this.gl; + + // FIXME + this.lineShader.uniforms.uMatrix = this.uMatrix.val; + + // Set the line color + this.lineShader.uniforms.uColor = this.color.glArray; + + // Copy data into the stream buffer + gl.bufferData( + gl.ARRAY_BUFFER, + this.stream.subarray(0, points.length * 2), + gl.STREAM_DRAW + ); + + // FIXME: Configure vertex attrib pointers in `useShader` + gl.vertexAttribPointer( + this.lineShader.attributes.aVertex, + VERTEX_SIZE, + gl.FLOAT, + false, + 0, + 0 + ); + + // Draw the stream buffer + gl.drawArrays(open ? gl.LINE_STRIP : gl.LINE_LOOP, 0, points.length); + + // FIXME: Configure vertex attrib pointers in `useShader` + gl.vertexAttribPointer( + this.quadShader.attributes.aVertex, + VERTEX_SIZE, + gl.FLOAT, + false, + ELEMENT_OFFSET, + VERTEX_OFFSET + ); + gl.vertexAttribPointer( + this.quadShader.attributes.aColor, + COLOR_SIZE, + gl.FLOAT, + false, + ELEMENT_OFFSET, + COLOR_OFFSET + ); + gl.vertexAttribPointer( + this.quadShader.attributes.aTexture, + TEXTURE_SIZE, + gl.FLOAT, + false, + ELEMENT_OFFSET, + TEXTURE_OFFSET + ); + gl.vertexAttribPointer( + this.quadShader.attributes.aRegion, + REGION_SIZE, + gl.FLOAT, + false, + ELEMENT_OFFSET, + REGION_OFFSET + ); + }, + + /** + * Set the line width + * @name lineWidth + * @memberOf me.WebGLRenderer.Compositor + * @function + * @param {Number} width Line width + */ + lineWidth : function (width) { + this.gl.lineWidth(width); + }, + + /** + * Clear the frame buffer, flushes the composite operations and calls + * gl.clear() + * @name clear + * @memberOf me.WebGLRenderer.Compositor + * @function + */ + clear : function () { + this.flush(); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org/ + * + */ +(function () { + /** + * @namespace me.input + * @memberOf me + */ + me.input = (function () { + // hold public stuff in our singleton + var api = {}; + + /* + * PRIVATE STUFF + */ + + /** + * prevent event propagation + * @ignore + */ + api._preventDefault = function (e) { + // stop event propagation + if (e.stopPropagation) { + e.stopPropagation(); + } + else { + e.cancelBubble = true; + } + // stop event default processing + if (e.preventDefault) { + e.preventDefault(); + } + else { + e.returnValue = false; + } + + return false; + }; + + /* + * PUBLIC STUFF + */ + + /** + * Global flag to specify if melonJS should prevent default browser action on registered key events
+ * This is also configurable per key through the bindKey function + * default : true + * @public + * @type Boolean + * @name preventDefault + * @memberOf me.input + */ + api.preventDefault = true; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org/ + * + */ +(function (api) { + /* + * PRIVATE STUFF + */ + + // list of binded keys + api._KeyBinding = {}; + + // corresponding actions + var keyStatus = {}; + + // lock enable flag for keys + var keyLock = {}; + // actual lock status of each key + var keyLocked = {}; + + // List of binded keys being held + var keyRefs = {}; + + // whether default event should be prevented for a given keypress + var preventDefaultForKeys = {}; + + // some useful flags + var keyboardInitialized = false; + + /** + * enable keyboard event + * @ignore + */ + api._enableKeyboardEvent = function () { + if (!keyboardInitialized) { + window.addEventListener("keydown", api._keydown, false); + window.addEventListener("keyup", api._keyup, false); + keyboardInitialized = true; + } + }; + + /** + * key down event + * @ignore + */ + api._keydown = function (e, keyCode, mouseButton) { + + keyCode = keyCode || e.keyCode || e.which; + var action = api._KeyBinding[keyCode]; + + // publish a message for keydown event + me.event.publish(me.event.KEYDOWN, [ + action, + keyCode, + action ? !keyLocked[action] : true + ]); + + if (action) { + if (!keyLocked[action]) { + var trigger = mouseButton ? mouseButton : keyCode; + if (!keyRefs[action][trigger]) { + keyStatus[action]++; + keyRefs[action][trigger] = true; + } + } + // prevent event propagation + if (preventDefaultForKeys[keyCode]) { + return api._preventDefault(e); + } + else { + return true; + } + } + + return true; + }; + + + /** + * key up event + * @ignore + */ + api._keyup = function (e, keyCode, mouseButton) { + keyCode = keyCode || e.keyCode || e.which; + var action = api._KeyBinding[keyCode]; + + // publish a message for keydown event + me.event.publish(me.event.KEYUP, [ action, keyCode ]); + + if (action) { + var trigger = mouseButton ? mouseButton : keyCode; + keyRefs[action][trigger] = undefined; + + if (keyStatus[action] > 0) { + keyStatus[action]--; + } + + keyLocked[action] = false; + + // prevent event propagation + if (preventDefaultForKeys[keyCode]) { + return api._preventDefault(e); + } + else { + return true; + } + } + + return true; + }; + + /* + * PUBLIC STUFF + */ + + /** + * standard keyboard constants + * @public + * @enum {number} + * @namespace KEY + * @memberOf me.input + */ + api.KEY = { + /** @memberOf me.input.KEY */ + "BACKSPACE" : 8, + /** @memberOf me.input.KEY */ + "TAB" : 9, + /** @memberOf me.input.KEY */ + "ENTER" : 13, + /** @memberOf me.input.KEY */ + "SHIFT" : 16, + /** @memberOf me.input.KEY */ + "CTRL" : 17, + /** @memberOf me.input.KEY */ + "ALT" : 18, + /** @memberOf me.input.KEY */ + "PAUSE" : 19, + /** @memberOf me.input.KEY */ + "CAPS_LOCK" : 20, + /** @memberOf me.input.KEY */ + "ESC" : 27, + /** @memberOf me.input.KEY */ + "SPACE" : 32, + /** @memberOf me.input.KEY */ + "PAGE_UP" : 33, + /** @memberOf me.input.KEY */ + "PAGE_DOWN" : 34, + /** @memberOf me.input.KEY */ + "END" : 35, + /** @memberOf me.input.KEY */ + "HOME" : 36, + /** @memberOf me.input.KEY */ + "LEFT" : 37, + /** @memberOf me.input.KEY */ + "UP" : 38, + /** @memberOf me.input.KEY */ + "RIGHT" : 39, + /** @memberOf me.input.KEY */ + "DOWN" : 40, + /** @memberOf me.input.KEY */ + "PRINT_SCREEN" : 42, + /** @memberOf me.input.KEY */ + "INSERT" : 45, + /** @memberOf me.input.KEY */ + "DELETE" : 46, + /** @memberOf me.input.KEY */ + "NUM0" : 48, + /** @memberOf me.input.KEY */ + "NUM1" : 49, + /** @memberOf me.input.KEY */ + "NUM2" : 50, + /** @memberOf me.input.KEY */ + "NUM3" : 51, + /** @memberOf me.input.KEY */ + "NUM4" : 52, + /** @memberOf me.input.KEY */ + "NUM5" : 53, + /** @memberOf me.input.KEY */ + "NUM6" : 54, + /** @memberOf me.input.KEY */ + "NUM7" : 55, + /** @memberOf me.input.KEY */ + "NUM8" : 56, + /** @memberOf me.input.KEY */ + "NUM9" : 57, + /** @memberOf me.input.KEY */ + "A" : 65, + /** @memberOf me.input.KEY */ + "B" : 66, + /** @memberOf me.input.KEY */ + "C" : 67, + /** @memberOf me.input.KEY */ + "D" : 68, + /** @memberOf me.input.KEY */ + "E" : 69, + /** @memberOf me.input.KEY */ + "F" : 70, + /** @memberOf me.input.KEY */ + "G" : 71, + /** @memberOf me.input.KEY */ + "H" : 72, + /** @memberOf me.input.KEY */ + "I" : 73, + /** @memberOf me.input.KEY */ + "J" : 74, + /** @memberOf me.input.KEY */ + "K" : 75, + /** @memberOf me.input.KEY */ + "L" : 76, + /** @memberOf me.input.KEY */ + "M" : 77, + /** @memberOf me.input.KEY */ + "N" : 78, + /** @memberOf me.input.KEY */ + "O" : 79, + /** @memberOf me.input.KEY */ + "P" : 80, + /** @memberOf me.input.KEY */ + "Q" : 81, + /** @memberOf me.input.KEY */ + "R" : 82, + /** @memberOf me.input.KEY */ + "S" : 83, + /** @memberOf me.input.KEY */ + "T" : 84, + /** @memberOf me.input.KEY */ + "U" : 85, + /** @memberOf me.input.KEY */ + "V" : 86, + /** @memberOf me.input.KEY */ + "W" : 87, + /** @memberOf me.input.KEY */ + "X" : 88, + /** @memberOf me.input.KEY */ + "Y" : 89, + /** @memberOf me.input.KEY */ + "Z" : 90, + /** @memberOf me.input.KEY */ + "WINDOW_KEY" : 91, + /** @memberOf me.input.KEY */ + "NUMPAD0" : 96, + /** @memberOf me.input.KEY */ + "NUMPAD1" : 97, + /** @memberOf me.input.KEY */ + "NUMPAD2" : 98, + /** @memberOf me.input.KEY */ + "NUMPAD3" : 99, + /** @memberOf me.input.KEY */ + "NUMPAD4" : 100, + /** @memberOf me.input.KEY */ + "NUMPAD5" : 101, + /** @memberOf me.input.KEY */ + "NUMPAD6" : 102, + /** @memberOf me.input.KEY */ + "NUMPAD7" : 103, + /** @memberOf me.input.KEY */ + "NUMPAD8" : 104, + /** @memberOf me.input.KEY */ + "NUMPAD9" : 105, + /** @memberOf me.input.KEY */ + "MULTIPLY" : 106, + /** @memberOf me.input.KEY */ + "ADD" : 107, + /** @memberOf me.input.KEY */ + "SUBSTRACT" : 109, + /** @memberOf me.input.KEY */ + "DECIMAL" : 110, + /** @memberOf me.input.KEY */ + "DIVIDE" : 111, + /** @memberOf me.input.KEY */ + "F1" : 112, + /** @memberOf me.input.KEY */ + "F2" : 113, + /** @memberOf me.input.KEY */ + "F3" : 114, + /** @memberOf me.input.KEY */ + "F4" : 115, + /** @memberOf me.input.KEY */ + "F5" : 116, + /** @memberOf me.input.KEY */ + "F6" : 117, + /** @memberOf me.input.KEY */ + "F7" : 118, + /** @memberOf me.input.KEY */ + "F8" : 119, + /** @memberOf me.input.KEY */ + "F9" : 120, + /** @memberOf me.input.KEY */ + "F10" : 121, + /** @memberOf me.input.KEY */ + "F11" : 122, + /** @memberOf me.input.KEY */ + "F12" : 123, + /** @memberOf me.input.KEY */ + "NUM_LOCK" : 144, + /** @memberOf me.input.KEY */ + "SCROLL_LOCK" : 145, + /** @memberOf me.input.KEY */ + "SEMICOLON" : 186, + /** @memberOf me.input.KEY */ + "PLUS" : 187, + /** @memberOf me.input.KEY */ + "COMMA" : 188, + /** @memberOf me.input.KEY */ + "MINUS" : 189, + /** @memberOf me.input.KEY */ + "PERIOD" : 190, + /** @memberOf me.input.KEY */ + "FORWAND_SLASH" : 191, + /** @memberOf me.input.KEY */ + "GRAVE_ACCENT" : 192, + /** @memberOf me.input.KEY */ + "OPEN_BRACKET" : 219, + /** @memberOf me.input.KEY */ + "BACK_SLASH" : 220, + /** @memberOf me.input.KEY */ + "CLOSE_BRACKET" : 221, + /** @memberOf me.input.KEY */ + "SINGLE_QUOTE" : 222 + }; + + /** + * return the key press status of the specified action + * @name isKeyPressed + * @memberOf me.input + * @public + * @function + * @param {String} action user defined corresponding action + * @return {Boolean} true if pressed + * @example + * if (me.input.isKeyPressed('left')) + * { + * //do something + * } + * else if (me.input.isKeyPressed('right')) + * { + * //do something else... + * } + * + */ + api.isKeyPressed = function (action) { + if (keyStatus[action] && !keyLocked[action]) { + if (keyLock[action]) { + keyLocked[action] = true; + } + return true; + } + return false; + }; + + /** + * return the key status of the specified action + * @name keyStatus + * @memberOf me.input + * @public + * @function + * @param {String} action user defined corresponding action + * @return {Boolean} down (true) or up(false) + */ + api.keyStatus = function (action) { + return (keyStatus[action] > 0); + }; + + + /** + * trigger the specified key (simulated) event
+ * @name triggerKeyEvent + * @memberOf me.input + * @public + * @function + * @param {me.input.KEY} keycode + * @param {Boolean} [status=false] true to trigger a key press, or false for key release + * @example + * // trigger a key press + * me.input.triggerKeyEvent(me.input.KEY.LEFT, true); + */ + api.triggerKeyEvent = function (keycode, status) { + if (status) { + api._keydown({}, keycode); + } + else { + api._keyup({}, keycode); + } + }; + + + /** + * associate a user defined action to a keycode + * @name bindKey + * @memberOf me.input + * @public + * @function + * @param {me.input.KEY} keycode + * @param {String} action user defined corresponding action + * @param {Boolean} [lock=false] cancel the keypress event once read + * @param {Boolean} [preventDefault=me.input.preventDefault] prevent default browser action + * @example + * // enable the keyboard + * me.input.bindKey(me.input.KEY.LEFT, "left"); + * me.input.bindKey(me.input.KEY.RIGHT, "right"); + * me.input.bindKey(me.input.KEY.X, "jump", true); + * me.input.bindKey(me.input.KEY.F1, "options", true, true); + */ + api.bindKey = function (keycode, action, lock, preventDefault) { + // make sure the keyboard is enable + api._enableKeyboardEvent(); + + if (typeof preventDefault !== "boolean") { + preventDefault = api.preventDefault; + } + + api._KeyBinding[keycode] = action; + preventDefaultForKeys[keycode] = preventDefault; + + keyStatus[action] = 0; + keyLock[action] = lock ? lock : false; + keyLocked[action] = false; + keyRefs[action] = {}; + }; + + /** + * unlock a key manually + * @name unlockKey + * @memberOf me.input + * @public + * @function + * @param {String} action user defined corresponding action + * @example + * // Unlock jump when touching the ground + * if (!this.falling && !this.jumping) { + * me.input.unlockKey("jump"); + * } + */ + api.unlockKey = function (action) { + keyLocked[action] = false; + }; + + /** + * unbind the defined keycode + * @name unbindKey + * @memberOf me.input + * @public + * @function + * @param {me.input.KEY} keycode + * @example + * me.input.unbindKey(me.input.KEY.LEFT); + */ + api.unbindKey = function (keycode) { + // clear the event status + var keybinding = api._KeyBinding[keycode]; + keyStatus[keybinding] = 0; + keyLock[keybinding] = false; + keyRefs[keybinding] = {}; + // remove the key binding + api._KeyBinding[keycode] = null; + preventDefaultForKeys[keycode] = null; + }; +})(me.input); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org/ + * + */ +(function (api) { + /** + * The built in Event Object + * @external Event + * @see {@link https://developer.mozilla.org/en/docs/Web/API/Event|Event} + */ + + /** + * Event normalized X coordinate within the game canvas itself
+ * + * @memberof! external:Event# + * @name external:Event#gameX + * @type {Number} + */ + + /** + * Event normalized Y coordinate within the game canvas itself
+ * + * @memberof! external:Event# + * @name external:Event#gameY + * @type {Number} + */ + + /** + * Event X coordinate relative to the viewport + * @memberof! external:Event# + * @name external:Event#gameScreenX + * @type {Number} + */ + + /** + * Event Y coordinate relative to the viewport + * @memberof! external:Event# + * @name external:Event#gameScreenY + * @type {Number} + */ + + /** + * Event X coordinate relative to the map + * @memberof! external:Event# + * @name external:Event#gameWorldX + * @type {Number} + */ + + /** + * Event Y coordinate relative to the map + * @memberof! external:Event# + * @name external:Event#gameWorldY + * @type {Number} + */ + + /** + * Event X coordinate relative to the holding container + * @memberof! external:Event# + * @name external:Event#gameLocalX + * @type {Number} + */ + + /** + * Event Y coordinate relative to the holding container + * @memberof! external:Event# + * @name external:Event#gameLocalY + * @type {Number} + */ + + /** + * The unique identifier of the contact for a touch, mouse or pen
+ * (This id is also defined on non Pointer Event Compatible platform like pure mouse or iOS-like touch event) + * @memberof! external:Event# + * @name external:Event#pointerId + * @type {Number} + * @see http://msdn.microsoft.com/en-us/library/windows/apps/hh466123.aspx + */ + + /* + * PRIVATE STUFF + */ + + /** + * A pool of `Vector` objects to cache pointer/touch event coordinates. + * @type {Array.} + */ + var T_VECTORS = []; + for (var v = 0; v < 10; v++) { T_VECTORS.push(new me.Vector2d()); } + + // list of registered Event handlers + var evtHandlers = new Map(); + + // current pointer + var currentPointer = new me.Rect(0, 0, 1, 1); + + // some useful flags + var pointerInitialized = false; + + // to keep track of the supported wheel event + var wheeltype = "mousewheel"; + + // Track last event timestamp to prevent firing events out of order + var lastTimeStamp = 0; + + // "active" list of supported events + var activeEventList = []; + + // internal constants + var MOUSE_WHEEL = ["mousewheel"]; + var POINTER_MOVE = ["pointermove", "MSPointerMove", "mousemove", "touchmove"]; + var POINTER_DOWN = ["pointerdown", "MSPointerDown", "mousedown", "touchstart"]; + var POINTER_UP = ["pointerup", "MSPointerUp", "mouseup", "touchend"]; + var POINTER_CANCEL = ["pointercancel", "MSPointerCancel", "mousecancel", "touchcancel"]; + var POINTER_ENTER = ["pointerenter", "MSPointerEnter", "mouseenter", "touchenter"]; + var POINTER_LEAVE = ["pointerleave", "MSPointerLeave", "mouseleave", "touchleave"]; + + // list of standard pointer event type + var pointerEventList = [ + MOUSE_WHEEL[0], + POINTER_MOVE[0], + POINTER_DOWN[0], + POINTER_UP[0], + POINTER_CANCEL[0], + POINTER_ENTER[0], + POINTER_LEAVE[0] + ]; + + // previous MS prefixed pointer event type + var MSPointerEventList = [ + MOUSE_WHEEL[0], + POINTER_MOVE[1], + POINTER_DOWN[1], + POINTER_UP[1], + POINTER_CANCEL[1], + POINTER_ENTER[1], + POINTER_LEAVE[1] + ]; + + // legacy mouse event type + var mouseEventList = [ + MOUSE_WHEEL[0], + POINTER_MOVE[2], + POINTER_DOWN[2], + POINTER_UP[2], + POINTER_CANCEL[2], + POINTER_ENTER[2], + POINTER_LEAVE[2] + ]; + + // iOS style touch event type + var touchEventList = [ + POINTER_MOVE[3], + POINTER_DOWN[3], + POINTER_UP[3], + POINTER_CANCEL[3], + POINTER_ENTER[3], + POINTER_LEAVE[3] + ]; + + var pointerEventMap = { + mousewheel : MOUSE_WHEEL, + pointermove: POINTER_MOVE, + pointerdown: POINTER_DOWN, + pointerup: POINTER_UP, + pointercancel: POINTER_CANCEL, + pointerenter: POINTER_ENTER, + pointerleave: POINTER_LEAVE + }; + + /** + * cache value for the offset of the canvas position within the page + * @ignore + */ + var viewportOffset = new me.Vector2d(); + + /** + * Array of object containing changed touch information (iOS event model) + * @ignore + */ + var changedTouches = []; + + /** + * cache value for the offset of the canvas position within the page + * @ignore + */ + api._offset = null; + + /** + * addEventListerner for the specified event list and callback + * @ignore + */ + function registerEventListener(eventList, callback) { + for (var x = 0; x < eventList.length; ++x) { + if (eventList[x] !== POINTER_MOVE[0] && eventList[x] !== POINTER_MOVE[1] && + eventList[x] !== POINTER_MOVE[2] && eventList[x] !== POINTER_MOVE[3]) { + me.video.renderer.getScreenCanvas().addEventListener(eventList[x], callback, false); + } + } + } + + /** + * enable pointer event (MSPointer/Mouse/Touch) + * @ignore + */ + function enablePointerEvent() { + if (!pointerInitialized) { + // get relative canvas position in the page + api._offset = me.video.getPos(); + // Automatically update relative canvas position on scroll + window.addEventListener("scroll", throttle(100, false, + function (e) { + api._offset = me.video.getPos(); + me.event.publish(me.event.WINDOW_ONSCROLL, [ e ]); + } + ), false); + + // check standard + if (window.PointerEvent) { + activeEventList = pointerEventList; + } + else if (window.MSPointerEvent) { // check for backward compatibility with the 'MS' prefix + activeEventList = MSPointerEventList; + } + else { // Regular Mouse events + activeEventList = mouseEventList; + } + + if (me.device.touch && me.device.isMobile) { // `touch****` events for iOS/Android devices + activeEventList = activeEventList.concat(touchEventList); + } + + registerEventListener(activeEventList, onPointerEvent); + + // detect wheel event support + // Modern browsers support "wheel", Webkit and IE support at least "mousewheel + wheeltype = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel"; + window.addEventListener(wheeltype, onMouseWheel, false); + + // set the PointerMove/touchMove/MouseMove event + if (typeof(api.throttlingInterval) === "undefined") { + // set the default value + api.throttlingInterval = ~~(1000 / me.sys.fps); + } + // if time interval <= 16, disable the feature + var i; + var events = findAllActiveEvents(activeEventList, POINTER_MOVE); + if (api.throttlingInterval < 17) { + for (i = 0; i < events.length; i++) { + if (activeEventList.indexOf(events[i]) !== -1) { + me.video.renderer.getScreenCanvas().addEventListener( + events[i], + onMoveEvent, + false + ); + } + + } + } + else { + for (i = 0; i < events.length; i++) { + if (activeEventList.indexOf(events[i]) !== -1) { + me.video.renderer.getScreenCanvas().addEventListener( + events[i], + throttle( + api.throttlingInterval, + false, + onMoveEvent + ), + false + ); + } + } + } + pointerInitialized = true; + } + } + + /** + * @ignore + */ + function findActiveEvent(activeEventList, eventTypes) { + for (var i = 0; i < eventTypes.length; i++) { + var event = activeEventList.indexOf(eventTypes[i]); + if (event !== -1) { + return eventTypes[i]; + } + } + } + + /** + * @ignore + */ + function findAllActiveEvents(activeEventList, eventTypes) { + var events = []; + for (var i = 0; i < eventTypes.length; i++) { + var event = activeEventList.indexOf(eventTypes[i]); + if (event !== -1) { + events.push(eventTypes[i]); + } + } + + return events; + } + + /** + * @ignore + */ + function triggerEvent(handlers, type, e, pointerId) { + var callback; + if (handlers.callbacks[type]) { + handlers.pointerId = pointerId; + for (var i = handlers.callbacks[type].length - 1; (callback = handlers.callbacks[type][i]); i--) { + if (callback(e) === false) { + // stop propagating the event if return false + return true; + } + } + } + return false; + } + + /** + * propagate events to registered objects + * @ignore + */ + function dispatchEvent(e) { + var handled = false; + + // get the current screen to world offset + me.game.viewport.localToWorld(0, 0, viewportOffset); + + while (changedTouches.length > 0) { + + // keep a reference to the last item + var changedTouch = changedTouches.pop(); + // and put it back into our cache + T_VECTORS.push(changedTouch); + + // Do not fire older events + if (typeof(e.timeStamp) !== "undefined") { + if (e.timeStamp < lastTimeStamp) { + continue; + } + lastTimeStamp = e.timeStamp; + } + + // if PointerEvent is not supported + if (!me.device.pointerEvent) { + // -> define pointerId to simulate the PointerEvent standard + e.pointerId = changedTouch.id; + } + + /* Initialize the two coordinate space properties. */ + e.gameScreenX = changedTouch.x; + e.gameScreenY = changedTouch.y; + e.gameWorldX = e.gameScreenX + viewportOffset.x; + e.gameWorldY = e.gameScreenY + viewportOffset.y; + + currentPointer.setShape( + e.gameWorldX, + e.gameWorldY, + e.width || 1, + e.height || 1 + ); + + var candidates = me.collision.quadTree.retrieve(currentPointer, me.Container.prototype._sortReverseZ); + + // add the viewport to the list of candidates + candidates.push( me.game.viewport ); + + for (var c = candidates.length, candidate; c--, (candidate = candidates[c]);) { + + if (evtHandlers.has(candidate)) { + var handlers = evtHandlers.get(candidate); + var region = handlers.region; + var ancestor = region.ancestor; + var bounds = region.getBounds(); + + if (region.floating === true) { + e.gameX = e.gameLocalX = e.gameScreenX; + e.gameY = e.gameLocalY = e.gameScreenY; + } else { + e.gameX = e.gameLocalX = e.gameWorldX; + e.gameY = e.gameLocalY = e.gameWorldY; + } + // adjust gameLocalX to specify coordinates + // within the region ancestor container + if (typeof ancestor !== "undefined") { + var parentPos = ancestor.getBounds().pos; + e.gameLocalX = e.gameX - parentPos.x; + e.gameLocalY = e.gameY - parentPos.y; + } + + var eventInBounds = + // check the shape bounding box first + bounds.containsPoint(e.gameX, e.gameY) && + // then check more precisely if needed + (bounds === region || region.containsPoint(e.gameLocalX, e.gameLocalY)); + + switch (e.type) { + case POINTER_MOVE[0]: + case POINTER_MOVE[1]: + case POINTER_MOVE[2]: + case POINTER_MOVE[3]: + // moved out of bounds: trigger the POINTER_LEAVE callbacks + if (handlers.pointerId === e.pointerId && !eventInBounds) { + if (triggerEvent(handlers, findActiveEvent(activeEventList, POINTER_LEAVE), e, null)) { + handled = true; + break; + } + } + // no pointer & moved inside of bounds: trigger the POINTER_ENTER callbacks + else if (handlers.pointerId === null && eventInBounds) { + if (triggerEvent(handlers, findActiveEvent(activeEventList, POINTER_ENTER), e, e.pointerId)) { + handled = true; + break; + } + } + + // trigger the POINTER_MOVE callbacks + if (eventInBounds && triggerEvent(handlers, e.type, e, e.pointerId)) { + handled = true; + break; + } + break; + + case POINTER_UP[0]: + case POINTER_UP[1]: + case POINTER_UP[2]: + case POINTER_UP[3]: + // pointer defined & inside of bounds: trigger the POINTER_UP callback + if (handlers.pointerId === e.pointerId && eventInBounds) { + // trigger the corresponding callback + if (triggerEvent(handlers, e.type, e, null)) { + handled = true; + break; + } + } + break; + + case POINTER_CANCEL[0]: + case POINTER_CANCEL[1]: + case POINTER_CANCEL[2]: + case POINTER_CANCEL[3]: + // pointer defined: trigger the POINTER_CANCEL callback + if (handlers.pointerId === e.pointerId) { + // trigger the corresponding callback + if (triggerEvent(handlers, e.type, e, null)) { + handled = true; + break; + } + } + break; + + default: + // event inside of bounds: trigger the POINTER_DOWN or MOUSE_WHEEL callback + if (eventInBounds) { + + // trigger the corresponding callback + var type = e.type; + if (type === "wheel") { + type = "mousewheel"; + } + if (triggerEvent(handlers, type, e, e.pointerId)) { + handled = true; + break; + } + } + break; + } + } + if (handled === true) { + // stop iterating through this list of candidates + break; + } + } + } + return handled; + } + + /** + * translate event coordinates + * @ignore + */ + function updateCoordFromEvent(event) { + // PointerEvent or standard Mouse event + if (!event.touches) { + var pointerV = T_VECTORS.pop(); + api.globalToLocal(event.clientX, event.clientY, pointerV); + pointerV.id = event.pointerId || 1; + changedTouches.push(pointerV); + } + // iOS/Android like touch event + else { + for (var i = 0, l = event.changedTouches.length; i < l; i++) { + var touchV = T_VECTORS.pop(); + var t = event.changedTouches[i]; + api.globalToLocal(t.clientX, t.clientY, touchV); + touchV.id = t.identifier; + changedTouches.push(touchV); + } + } + // if event.isPrimary is defined and false, return + if (event.isPrimary === false) { + return; + } + + // Else use the first entry to simulate mouse event + api.pointer.pos.set( + changedTouches[0].x, + changedTouches[0].y + ); + + if (typeof(event.width) === "number") { + // resize the pointer object if necessary + if (event.width !== api.pointer.width || event.height !== api.pointer.height) { + api.pointer.resize(event.width || 1, event.height || 1); + } + } + } + + + /** + * mouse event management (mousewheel) + * @ignore + */ + function onMouseWheel(e) { + /* jshint expr:true */ + if (e.target === me.video.renderer.getScreenCanvas()) { + // create a (fake) normalized event object + updateCoordFromEvent(e); + e.deltaMode = 1; + if (wheeltype === "mousewheel") { + e.deltaY = - 1 / 40 * e.wheelDelta; + // Webkit also support wheelDeltaX + e.wheelDeltaX && (e.deltaX = - 1 / 40 * e.wheelDeltaX); + } + // dispatch mouse event to registered object + if (dispatchEvent(e)) { + // prevent default action + return api._preventDefault(e); + } + } + return true; + } + + + /** + * mouse/touch/pointer event management (move) + * @ignore + */ + function onMoveEvent(e) { + // update position + updateCoordFromEvent(e); + // dispatch mouse event to registered object + if (dispatchEvent(e)) { + // prevent default action + return api._preventDefault(e); + } + return true; + } + + /** + * mouse/touch/pointer event management (start/down, end/up) + * @ignore + */ + function onPointerEvent(e) { + // update the pointer position + updateCoordFromEvent(e); + + // dispatch event to registered objects + if (dispatchEvent(e)) { + // prevent default action + return api._preventDefault(e); + } + + // in case of touch event button is undefined + var button = e.button || 0; + var keycode = api.pointer.bind[button]; + + // check if mapped to a key + if (keycode) { + if (e.type === POINTER_DOWN[0] || e.type === POINTER_DOWN[1] || e.type === POINTER_DOWN[2]) { + return api._keydown(e, keycode, button + 1); + } + else { // 'mouseup' or 'touchend' + return api._keyup(e, keycode, button + 1); + } + } + + return true; + } + + /* + * PUBLIC STUFF + */ + + /** + * Pointer information (current position and size)
+ * properties :
+ * LEFT : constant for left button
+ * MIDDLE : constant for middle button
+ * RIGHT : constant for right button + * @public + * @type {me.Rect} + * @name pointer + * @memberOf me.input + */ + api.pointer = new me.Rect(0, 0, 1, 1); + + // bind list for mouse buttons + api.pointer.bind = [ 0, 0, 0 ]; + + // W3C button constants + api.pointer.LEFT = 0; + api.pointer.MIDDLE = 1; + api.pointer.RIGHT = 2; + + /** + * time interval for event throttling in milliseconds
+ * default value : "1000/me.sys.fps" ms
+ * set to 0 ms to disable the feature + * @public + * @type Number + * @name throttlingInterval + * @memberOf me.input + */ + api.throttlingInterval = undefined; + + /** + * Translate the specified x and y values from the global (absolute) + * coordinate to local (viewport) relative coordinate. + * @name globalToLocal + * @memberOf me.input + * @public + * @function + * @param {Number} x the global x coordinate to be translated. + * @param {Number} y the global y coordinate to be translated. + * @param {Number} [v] an optional vector object where to set the + * @return {me.Vector2d} A vector object with the corresponding translated coordinates. + * @example + * onMouseEvent : function (e) { + * // convert the given into local (viewport) relative coordinates + * var pos = me.input.globalToLocal(e.clientX, e,clientY); + * // do something with pos ! + * }; + */ + api.globalToLocal = function (x, y, v) { + v = v || new me.Vector2d(); + var offset = api._offset; + var pixelRatio = me.device.getPixelRatio(); + x -= offset.left; + y -= offset.top; + var scale = me.sys.scale; + if (scale.x !== 1.0 || scale.y !== 1.0) { + x /= scale.x; + y /= scale.y; + } + return v.set(x * pixelRatio, y * pixelRatio); + }; + + /** + * Associate a pointer event to a keycode
+ * Left button – 0 + * Middle button – 1 + * Right button – 2 + * @name bindPointer + * @memberOf me.input + * @public + * @function + * @param {Number} [button=me.input.pointer.LEFT] (accordingly to W3C values : 0,1,2 for left, middle and right buttons) + * @param {me.input.KEY} keyCode + * @example + * // enable the keyboard + * me.input.bindKey(me.input.KEY.X, "shoot"); + * // map the left button click on the X key (default if the button is not specified) + * me.input.bindPointer(me.input.KEY.X); + * // map the right button click on the X key + * me.input.bindPointer(me.input.pointer.RIGHT, me.input.KEY.X); + */ + api.bindPointer = function () { + var button = (arguments.length < 2) ? api.pointer.LEFT : arguments[0]; + var keyCode = (arguments.length < 2) ? arguments[0] : arguments[1]; + + // make sure the mouse is initialized + enablePointerEvent(); + + // throw an exception if no action is defined for the specified keycode + if (!api._KeyBinding[keyCode]) { + throw new me.Error("no action defined for keycode " + keyCode); + } + // map the mouse button to the keycode + api.pointer.bind[button] = keyCode; + }; + /** + * unbind the defined keycode + * @name unbindPointer + * @memberOf me.input + * @public + * @function + * @param {Number} [button=me.input.pointer.LEFT] (accordingly to W3C values : 0,1,2 for left, middle and right buttons) + * @example + * me.input.unbindPointer(me.input.pointer.LEFT); + */ + api.unbindPointer = function (button) { + // clear the event status + api.pointer.bind[ + typeof(button) === "undefined" ? + api.pointer.LEFT : button + ] = null; + }; + + + /** + * allows registration of event listeners on the object target.
+ * melonJS defines the additional `gameX` and `gameY` properties when passing the Event object to the defined callback (see below)
+ * @see external:Event + * @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list} + * @name registerPointerEvent + * @memberOf me.input + * @public + * @function + * @param {String} eventType The event type for which the object is registering
+ * melonJS currently supports:
+ *

    + *
  • "pointermove"
  • + *
  • "pointerdown"
  • + *
  • "pointerup"
  • + *
  • "pointerenter"
  • + *
  • "pointerleave"
  • + *
  • "pointercancel"
  • + *
  • "mousewheel"
  • + *
+ * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} region a shape representing the region to register on + * @param {Function} callback methods to be called when the event occurs. + * Returning `false` from the defined callback will prevent the event to be propagated to other objects + * @example + * // onActivate function + * onActivateEvent: function () { + * // register on the 'pointerdown' event + * me.input.registerPointerEvent('pointerdown', this, this.pointerDown.bind(this)); + * }, + * + * // pointerDown event callback + * pointerDown: function (event) { + * // do something + * .... + * // don"t propagate the event to other objects + * return false; + * }, + */ + api.registerPointerEvent = function (eventType, region, callback) { + // make sure the mouse/touch events are initialized + enablePointerEvent(); + + if (pointerEventList.indexOf(eventType) === -1) { + throw new me.Error("invalid event type : " + eventType); + } + + var eventTypes = findAllActiveEvents(activeEventList, pointerEventMap[eventType]); + + // register the event + if (!evtHandlers.has(region)) { + evtHandlers.set(region, { + region : region, + callbacks : {}, + pointerId : null + }); + } + + // allocate array if not defined + var handlers = evtHandlers.get(region); + for (var i = 0; i < eventTypes.length; i++) { + eventType = eventTypes[i]; + if (handlers.callbacks[eventType]) { + handlers.callbacks[eventType].push(callback); + } else { + handlers.callbacks[eventType] = [callback]; + } + } + }; + + /** + * allows the removal of event listeners from the object target. + * @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list} + * @name releasePointerEvent + * @memberOf me.input + * @public + * @function + * @param {String} eventType The event type for which the object was registered. See {@link me.input.registerPointerEvent} + * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} region the registered region to release for this event + * @param {Function} [callback="all"] if specified unregister the event only for the specific callback + * @example + * // release the registered region on the 'pointerdown' event + * me.input.releasePointerEvent('pointerdown', this); + */ + api.releasePointerEvent = function (eventType, region, callback) { + if (pointerEventList.indexOf(eventType) === -1) { + throw new me.Error("invalid event type : " + eventType); + } + + // convert to supported event type if pointerEvent not natively supported + var eventTypes = findAllActiveEvents(activeEventList, pointerEventMap[eventType]); + + var handlers = evtHandlers.get(region); + for (var i = 0; i < eventTypes.length; i++) { + eventType = eventTypes[i]; + if (handlers.callbacks[eventType]) { + if (typeof (callback) !== "undefined") { + handlers.callbacks[eventType].remove(callback); + } else { + while (handlers.callbacks[eventType].length > 0) { + handlers.callbacks[eventType].pop(); + } + } + // free the array if empty + if (handlers.callbacks[eventType].length === 0) { + delete handlers.callbacks[eventType]; + } + } + } + if (Object.keys(handlers.callbacks).length === 0) { + evtHandlers.delete(region); + } + }; + + /** + * Will translate global (frequently used) pointer events + * which should be catched at root level, into minipubsub system events + * @name _translatePointerEvents + * @memberOf me.input + * @ignore + * @function + */ + api._translatePointerEvents = function () { + // listen to mouse move (and touch move) events on the viewport + // and convert them to a system event by default + api.registerPointerEvent("pointermove", me.game.viewport, function (e) { + me.event.publish(me.event.POINTERMOVE, [e]); + }); + }; +})(me.input); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2015, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org/ + * + */ +(function (api) { + /* + * PRIVATE STUFF + */ + + // Analog deadzone + var deadzone = 0.1; + + /** + * A function that returns a normalized value in range [-1.0..1.0], or 0.0 if the axis is unknown. + * @callback me.input~normalize_fn + * @param {Number} value The raw value read from the gamepad driver + * @param {Number} axis The axis index from the standard mapping, or -1 if not an axis + * @param {Number} button The button index from the standard mapping, or -1 if not a button + */ + function defaultNormalizeFn(value) { + return value; + } + + /** + * Normalize axis values for wired Xbox 360 + * @ignore + */ + function wiredXbox360NormalizeFn(value, axis, button) { + if (button === api.GAMEPAD.BUTTONS.L2 || button === api.GAMEPAD.BUTTONS.R2) { + return (value + 1) / 2; + } + return value; + } + + /** + * Normalize axis values for OUYA + * @ignore + */ + function ouyaNormalizeFn(value, axis, button) { + if (value > 0) { + if (button === api.GAMEPAD.BUTTONS.L2) { + // L2 is wonky; seems like the deadzone is around 20000 + // (That's over 15% of the total range!) + value = Math.max(0, value - 20000) / 111070; + } + else { + // Normalize [1..65536] => [0.0..0.5] + value = (value - 1) / 131070; + } + } + else { + // Normalize [-65536..-1] => [0.5..1.0] + value = (65536 + value) / 131070 + 0.5; + } + + return value; + } + + // Match vendor and product codes for Firefox + var vendorProductRE = /^([0-9a-f]{1,4})-([0-9a-f]{1,4})-/i; + + // Match leading zeros + var leadingZeroRE = /^0+/; + + /** + * Firefox reports different ids for gamepads depending on the platform: + * - Windows: vendor and product codes contain leading zeroes + * - Mac: vendor and product codes are sparse (no leading zeroes) + * + * This function normalizes the id to support both formats + * @ignore + */ + function addMapping(id, mapping) { + var expanded_id = id.replace(vendorProductRE, function (_, a, b) { + return ( + "000".substr(a.length - 1) + a + "-" + + "000".substr(b.length - 1) + b + "-" + ); + }); + var sparse_id = id.replace(vendorProductRE, function (_, a, b) { + return ( + a.replace(leadingZeroRE, "") + "-" + + b.replace(leadingZeroRE, "") + "-" + ); + }); + + // Normalize optional parameters + mapping.analog = mapping.analog || mapping.buttons.map(function () { + return -1; + }); + mapping.normalize_fn = mapping.normalize_fn || defaultNormalizeFn; + + remap.set(expanded_id, mapping); + remap.set(sparse_id, mapping); + } + + // binding list + var bindings = {}; + + // mapping list + var remap = new Map(); + + /** + * Default gamepad mappings + * @ignore + */ + [ + // Firefox mappings + [ + "45e-28e-Xbox 360 Wired Controller", + { + "axes" : [ 0, 1, 3, 4 ], + "buttons" : [ 11, 12, 13, 14, 8, 9, -1, -1, 5, 4, 6, 7, 0, 1, 2, 3, 10 ], + "analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + "normalize_fn" : wiredXbox360NormalizeFn + } + ], + [ + "54c-268-PLAYSTATION(R)3 Controller", + { + "axes" : [ 0, 1, 2, 3 ], + "buttons" : [ 14, 13, 15, 12, 10, 11, 8, 9, 0, 3, 1, 2, 4, 6, 7, 5, 16 ] + } + ], + [ + "54c-5c4-Wireless Controller", // PS4 Controller + { + "axes" : [ 0, 1, 2, 3 ], + "buttons" : [ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 12, 13 ] + } + ], + [ + "2836-1-OUYA Game Controller", + { + "axes" : [ 0, 3, 7, 9 ], + "buttons" : [ 3, 6, 4, 5, 7, 8, 15, 16, -1, -1, 9, 10, 11, 12, 13, 14, -1 ], + "analog" : [ -1, -1, -1, -1, -1, -1, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + "normalize_fn" : ouyaNormalizeFn + } + ], + + // Chrome mappings + [ + "OUYA Game Controller (Vendor: 2836 Product: 0001)", + { + "axes" : [ 0, 1, 3, 4 ], + "buttons" : [ 0, 3, 1, 2, 4, 5, 12, 13, -1, -1, 6, 7, 8, 9, 10, 11, -1 ], + "analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + "normalize_fn" : ouyaNormalizeFn + } + ] + ].forEach(function (value) { + addMapping(value[0], value[1]); + }); + + /** + * gamepad connected callback + * @ignore + */ + window.addEventListener("gamepadconnected", function (event) { + me.event.publish(me.event.GAMEPAD_CONNECTED, [ event.gamepad ]); + }, false); + + /** + * gamepad disconnected callback + * @ignore + */ + window.addEventListener("gamepaddisconnected", function (event) { + me.event.publish(me.event.GAMEPAD_DISCONNECTED, [ event.gamepad ]); + }, false); + + /** + * Update gamepad status + * @ignore + */ + api._updateGamepads = navigator.getGamepads ? function () { + var gamepads = navigator.getGamepads(); + var e = {}; + + // Trigger button bindings + Object.keys(bindings).forEach(function (index) { + var gamepad = gamepads[index]; + if (!gamepad) { + return; + } + + var mapping = null; + if (gamepad.mapping !== "standard") { + mapping = remap.get(gamepad.id); + } + + var binding = bindings[index]; + + // Iterate all buttons that have active bindings + Object.keys(binding.buttons).forEach(function (button) { + var last = binding.buttons[button]; + var mapped_button = button; + var mapped_axis = -1; + + // Remap buttons if necessary + if (mapping) { + mapped_button = mapping.buttons[button]; + mapped_axis = mapping.analog[button]; + if (mapped_button < 0 && mapped_axis < 0) { + // Button is not mapped + return; + } + } + + // Get mapped button + var current = gamepad.buttons[mapped_button] || {}; + + // Remap an axis to an analog button + if (mapping) { + if (mapped_axis >= 0) { + var value = mapping.normalize_fn(gamepad.axes[mapped_axis], -1, +button); + + // Create a new object, because GamepadButton is read-only + current = { + "value" : value, + "pressed" : current.pressed || (Math.abs(value) >= deadzone) + }; + } + } + + me.event.publish(me.event.GAMEPAD_UPDATE, [ index, "buttons", +button, current ]); + + // Edge detection + if (!last.pressed && current.pressed) { + api._keydown(e, last.keyCode, mapped_button + 256); + } + else if (last.pressed && !current.pressed) { + api._keyup(e, last.keyCode, mapped_button + 256); + } + + // Update last button state + last.value = current.value; + last.pressed = current.pressed; + }); + + // Iterate all axes that have active bindings + Object.keys(binding.axes).forEach(function (axis) { + var last = binding.axes[axis]; + var mapped_axis = axis; + + // Remap buttons if necessary + if (mapping) { + mapped_axis = mapping.axes[axis]; + if (mapped_axis < 0) { + // axe is not mapped + return; + } + } + + // retrieve the current value and normalize if necessary + var value = gamepad.axes[mapped_axis]; + if (typeof(value) === "undefined") { + return; + } + if (mapping) { + value = mapping.normalize_fn(value, +axis, -1); + } + // normalize value into a [-1, 1] range value (treat 0 as positive) + var range = Math.sign(value) || 1; + if (!last[range]) { + return; + } + var pressed = (Math.abs(value) >= (deadzone + Math.abs(last[range].threshold))); + + me.event.publish(me.event.GAMEPAD_UPDATE, [ index, "axes", +axis, value ]); + + // Edge detection + if (!last[range].pressed && pressed) { + api._keydown(e, last[range].keyCode, mapped_axis + 256); + } + else if ((last[range].pressed || (last[-range] && last[-range].pressed)) && !pressed) { + range = last[range].pressed ? range : -range; + api._keyup(e, last[range].keyCode, mapped_axis + 256); + } + + // Update last axis state + last[range].value = value; + last[range].pressed = pressed; + }); + }); + } : function () {}; + + /* + * PUBLIC STUFF + */ + + /** + * Namespace for standard gamepad mapping constants + * @public + * @namespace GAMEPAD + * @memberOf me.input + */ + api.GAMEPAD = { + /** + * Standard gamepad mapping information for axes
+ *
    + *
  • Left control stick: LX (horizontal), LY (vertical)
  • + *
  • Right control stick: RX (horizontal), RY (vertical)
  • + *
  • Extras: EXTRA_1, EXTRA_2, EXTRA_3, EXTRA_4
  • + *
+ * @public + * @name AXES + * @enum {Number} + * @memberOf me.input.GAMEPAD + * @see https://w3c.github.io/gamepad/#remapping + */ + "AXES" : { + "LX" : 0, + "LY" : 1, + "RX" : 2, + "RY" : 3, + "EXTRA_1" : 4, + "EXTRA_2" : 5, + "EXTRA_3" : 6, + "EXTRA_4" : 7 + }, + + /** + * Standard gamepad mapping information for buttons
+ *
    + *
  • Face buttons: FACE_1, FACE_2, FACE_3, FACE_4
  • + *
  • D-Pad: UP, DOWN, LEFT, RIGHT
  • + *
  • Shoulder buttons: L1, L2, R1, R2
  • + *
  • Analog stick (clicks): L3, R3
  • + *
  • Navigation: SELECT (BACK), START (FORWARD), HOME
  • + *
  • Extras: EXTRA_1, EXTRA_2, EXTRA_3, EXTRA_4
  • + *
+ * @public + * @name BUTTONS + * @enum {Number} + * @memberOf me.input.GAMEPAD + * @see https://w3c.github.io/gamepad/#remapping + */ + "BUTTONS" : { + "FACE_1" : 0, + "FACE_2" : 1, + "FACE_3" : 2, + "FACE_4" : 3, + "L1" : 4, + "R1" : 5, + "L2" : 6, + "R2" : 7, + "SELECT" : 8, + "BACK" : 8, + "START" : 9, + "FORWARD" : 9, + "L3" : 10, + "R3" : 11, + "UP" : 12, + "DOWN" : 13, + "LEFT" : 14, + "RIGHT" : 15, + "HOME" : 16, + "EXTRA_1" : 17, + "EXTRA_2" : 18, + "EXTRA_3" : 19, + "EXTRA_4" : 20 + } + }; + + /** + * Associate a gamepad event to a keycode + * @name bindGamepad + * @memberOf me.input + * @public + * @function + * @param {Number} index Gamepad index + * @param {me.input.GAMEPAD.BUTTONS|Object} button id (deprecated) or definition as below + * @param {String} button.type "buttons" or "axes" + * @param {me.input.GAMEPAD.BUTTONS|me.input.GAMEPAD.AXES} button.code button or axis code id + * @param {String} [button.threshold] value indicating when the axis should trigger the keycode (e.g. -0.5 or 0.5) + * @param {me.input.KEY} keyCode + * @example + * // enable the keyboard + * me.input.bindKey(me.input.KEY.X, "shoot"); + * ... + * // map the lower face button on the first gamepad to the X key (deprecated use) + * me.input.bindGamepad(0, me.input.GAMEPAD.BUTTONS.FACE_1, me.input.KEY.X); + * // map the lower face button on the first gamepad to the X key + * me.input.bindGamepad(0, {type:"buttons", code: me.input.GAMEPAD.BUTTONS.FACE_1}, me.input.KEY.X); + * // map the left axis value on the first gamepad to the LEFT key + * me.input.bindGamepad(0, {type:"axes", code: me.input.GAMEPAD.AXES.LX, threshold: -0.5}, me.input.KEY.LEFT); + */ + api.bindGamepad = function (index, button, keyCode) { + // Throw an exception if no action is defined for the specified keycode + if (!api._KeyBinding[keyCode]) { + throw new me.Error("no action defined for keycode " + keyCode); + } + + // for backward compatiblity with 3.0.x + if (typeof (button) !== "object") { + button = { + type : "buttons", + code : button + }; + console.warn("Deprecated: me.input.bindGamepad parameteres have changed"); + } + + // Allocate bindings if not defined + if (!bindings[index]) { + bindings[index] = { + "axes" : {}, + "buttons" : {} + }; + } + + var mapping = { + "keyCode" : keyCode, + "value" : 0, + "pressed" : false, + "threshold" : button.threshold // can be undefined + }; + var binding = bindings[index][button.type]; + + // Map the gamepad button or axis to the keycode + if (button.type === "buttons") { + // buttons are defined by a `gamePadButton` object + binding[button.code] = mapping; + } else if (button.type === "axes") { + // normalize threshold into a value that can represent both side of the axis + var range = (Math.sign(button.threshold) || 1); + // axes are defined using a double [] + if (!binding[button.code]) { + binding[button.code] = {}; + } + binding[button.code][range] = mapping; + } + }; + + /** + * unbind the defined keycode + * @name unbindGamepad + * @memberOf me.input + * @public + * @function + * @param {Number} index Gamepad index + * @param {me.input.GAMEPAD.BUTTONS} button + * @example + * me.input.unbindGamepad(0, me.input.GAMEPAD.BUTTONS.FACE_1); + */ + api.unbindGamepad = function (index, button) { + if (!bindings[index]) { + throw new me.Error("no bindings for gamepad " + index); + } + bindings[index].buttons[button] = {}; + }; + + /** + * Set deadzone for analog gamepad inputs
+ * The default deadzone is 0.1 (10%) Analog values less than this will be ignored + * @name setGamepadDeadzone + * @memberOf me.input + * @public + * @function + * @param {Number} value Deadzone value + */ + api.setGamepadDeadzone = function (value) { + deadzone = value; + }; + + /** + * specify a custom mapping for a specific gamepad id
+ * see below for the default mapping :
+ *

+ * @name setGamepadMapping + * @memberOf me.input + * @public + * @function + * @param {String} id Gamepad id string + * @param {Object} mapping A hash table + * @param {Number[]} mapping.axes Standard analog control stick axis locations + * @param {Number[]} mapping.buttons Standard digital button locations + * @param {Number[]} [mapping.analog] Analog axis locations for buttons + * @param {me.input~normalize_fn} [mapping.normalize_fn] Axis normalization function + * @example + * // A weird controller that has its axis mappings reversed + * me.input.setGamepadMapping("Generic USB Controller", { + * "axes" : [ 3, 2, 1, 0 ], + * "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ] + * }); + * + * // Mapping extra axes to analog buttons + * me.input.setGamepadMapping("Generic Analog Controller", { + * "axes" : [ 0, 1, 2, 3 ], + * "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ], + * + * // Raw axis 4 is mapped to GAMEPAD.BUTTONS.FACE_1 + * // Raw axis 5 is mapped to GAMEPAD.BUTTONS.FACE_2 + * // etc... + * // Also maps left and right triggers + * "analog" : [ 4, 5, 6, 7, -1, -1, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1 ], + * + * // Normalize the value of button L2: [-1.0..1.0] => [0.0..1.0] + * "normalize_fn" : function (value, axis, button) { + * return ((button === me.input.GAMEPAD.BUTTONS.L2) ? ((value + 1) / 2) : value) || 0; + * } + * }); + */ + api.setGamepadMapping = addMapping; + +})(me.input); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * Base64 decoding + * @see http://www.webtoolkit.info/ + * @ignore + */ + var Base64 = (function () { + // hold public stuff in our singleton + var singleton = {}; + + // private property + var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + // public method for decoding + singleton.decode = function (input) { + + // make sure our input string has the right format + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + if (me.device.nativeBase64) { + // use native decoder + return window.atob(input); + } + else { + // use cross-browser decoding + var output = [], chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0; + + while (i < input.length) { + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output.push(String.fromCharCode(chr1)); + + if (enc3 !== 64) { + output.push(String.fromCharCode(chr2)); + } + if (enc4 !== 64) { + output.push(String.fromCharCode(chr3)); + } + } + + output = output.join(""); + return output; + } + }; + + // public method for encoding + singleton.encode = function (input) { + + // make sure our input string has the right format + input = input.replace(/\r\n/g, "\n"); + + if (me.device.nativeBase64) { + // use native encoder + return window.btoa(input); + } + else { + // use cross-browser encoding + var output = [], chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0; + + + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output.push(_keyStr.charAt(enc1)); + output.push(_keyStr.charAt(enc2)); + output.push(_keyStr.charAt(enc3)); + output.push(_keyStr.charAt(enc4)); + } + + output = output.join(""); + return output; + } + }; + + return singleton; + + })(); + + /** + * a collection of utility functions
+ * there is no constructor function for me.utils + * @namespace me.utils + * @memberOf me + */ + me.utils = (function () { + // hold public stuff in our singleton + var api = {}; + + /* + * PRIVATE STUFF + */ + + // guid default value + var GUID_base = ""; + var GUID_index = 0; + + // regexp to deal with file name & path + var REMOVE_PATH = /^.*(\\|\/|\:)/; + var REMOVE_EXT = /\.[^\.]*$/; + + /* + * PUBLIC STUFF + */ + + /** + * Decode a base64 encoded string into a binary string + * @public + * @function + * @memberOf me.utils + * @name decodeBase64 + * @param {String} input Base64 encoded data + * @return {String} Binary string + */ + api.decodeBase64 = function (input) { + return Base64.decode(input); + }; + + /** + * Encode binary string into a base64 string + * @public + * @function + * @memberOf me.utils + * @name encodeBase64 + * @param {String} input Binary string + * @return {String} Base64 encoded data + */ + api.encodeBase64 = function (input) { + return Base64.encode(input); + }; + + /** + * Decode a base64 encoded string into a byte array + * @public + * @function + * @memberOf me.utils + * @name decodeBase64AsArray + * @param {String} input Base64 encoded data + * @param {Number} [bytes] number of bytes per array entry + * @return {Number[]} Decoded data + */ + api.decodeBase64AsArray = function (input, bytes) { + bytes = bytes || 1; + + var dec = Base64.decode(input), i, j, len; + var ar = new Uint32Array(dec.length / bytes); + + for (i = 0, len = dec.length / bytes; i < len; i++) { + ar[i] = 0; + for (j = bytes - 1; j >= 0; --j) { + ar[i] += dec.charCodeAt((i * bytes) + j) << (j << 3); + } + } + return ar; + }; + + /** + * decompress zlib/gzip data (NOT IMPLEMENTED) + * @public + * @function + * @memberOf me.utils + * @name decompress + * @param {Number[]} data Array of bytes + * @param {String} format compressed data format ("gzip","zlib") + * @return {Number[]} Decompressed data + */ + api.decompress = function () { + throw new me.Error("GZIP/ZLIB compressed TMX Tile Map not supported!"); + }; + + /** + * Decode a CSV encoded array into a binary array + * @public + * @function + * @memberOf me.utils + * @name decodeCSV + * @param {String} input CSV formatted data + * @return {Number[]} Decoded data + */ + api.decodeCSV = function (input) { + var entries = input.replace("\n", "").trim().split(","); + + var result = []; + for (var i = 0; i < entries.length; i++) { + result.push(+entries[i]); + } + return result; + }; + + /** + * return the base name of the file without path info.
+ * @public + * @function + * @memberOf me.utils + * @name getBasename + * @param {String} path path containing the filename + * @return {String} the base name without path information. + */ + api.getBasename = function (path) { + return path.replace(REMOVE_PATH, "").replace(REMOVE_EXT, ""); + }; + + /** + * return the extension of the file in the given path
+ * @public + * @function + * @memberOf me.utils + * @name getFileExtension + * @param {String} path path containing the filename + * @return {String} filename extension. + */ + api.getFileExtension = function (path) { + return path.substring(path.lastIndexOf(".") + 1, path.length); + }; + + /** + * Get image pixels + * @public + * @function + * @memberOf me.utils + * @name getPixels + * @param {Image|Canvas} image Image to read + * @return {ImageData} Canvas ImageData object + */ + api.getPixels = function (arg) { + if (arg instanceof HTMLImageElement) { + var _context = me.CanvasRenderer.getContext2d( + me.video.createCanvas(arg.width, arg.height) + ); + _context.drawImage(arg, 0, 0); + return _context.getImageData(0, 0, arg.width, arg.height); + } + else { + // canvas ! + return arg.getContext("2d").getImageData(0, 0, arg.width, arg.height); + } + }; + + /** + * Normalize a String or Image to an Image reference + * @public + * @function + * @memberOf me.utils + * @name getImage + * @param {Image|String} image Image name or Image reference + * @return {Image} Image reference + */ + api.getImage = function (image) { + return ( + (typeof(image) === "string") ? + me.loader.getImage(me.utils.getBasename(image)) : + image + ); + }; + + /** + * reset the GUID Base Name + * the idea here being to have a unique ID + * per level / object + * @ignore + */ + api.resetGUID = function (base, index) { + // also ensure it's only 8bit ASCII characters + GUID_base = base.toString().toUpperCase().toHex(); + GUID_index = index || 0; + }; + + /** + * create and return a very simple GUID + * Game Unique ID + * @ignore + */ + api.createGUID = function (index) { + // to cover the case of undefined id for groups + GUID_index += index || 1; + return GUID_base + "-" + (index || GUID_index); + }; + + /** + * returns true if the given value is a power of two + * @public + * @function + * @memberOf me.utils + * @name isPowerOfTwo + * @param {Number} val + * @return {boolean} + */ + api.isPowerOfTwo = function (val) { + return (val & (val - 1)) === 0; + }; + + /** + * returns the next power of two for the given value + * @public + * @function + * @memberOf me.utils + * @name nextPowerOfTwo + * @param {Number} val + * @return {boolean} + */ + api.nextPowerOfTwo = function (val) { + val --; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val ++; + return val; + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ +(function () { + var rgbaRx = /^rgba?\((\d+), ?(\d+), ?(\d+)(, ?([\d\.]+))?\)$/; + var hex3Rx = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])$/; + var hex4Rx = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])([\da-fA-F])$/; + var hex6Rx = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/; + var hex8Rx = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/; + + var cssToRGB = new Map(); + + [ + // CSS1 + [ "black", [ 0, 0, 0 ] ], + [ "silver", [ 192, 192, 129 ] ], + [ "gray", [ 128, 128, 128 ] ], + [ "white", [ 255, 255, 255 ] ], + [ "maroon", [ 128, 0, 0 ] ], + [ "red", [ 255, 0, 0 ] ], + [ "purple", [ 128, 0, 128 ] ], + [ "fuchsia", [ 255, 0, 255 ] ], + [ "green", [ 0, 128, 0 ] ], + [ "lime", [ 0, 255, 0 ] ], + [ "olive", [ 128, 128, 0 ] ], + [ "yellow", [ 255, 255, 0 ] ], + [ "navy", [ 0, 0, 128 ] ], + [ "blue", [ 0, 0, 255 ] ], + [ "teal", [ 0, 128, 128 ] ], + [ "aqua", [ 0, 255, 255 ] ], + + // CSS2 + [ "orange", [ 255, 165, 0 ] ], + + // CSS3 + [ "aliceblue", [ 240, 248, 245 ] ], + [ "antiquewhite", [ 250, 235, 215 ] ], + [ "aquamarine", [ 127, 255, 212 ] ], + [ "azure", [ 240, 255, 255 ] ], + [ "beige", [ 245, 245, 220 ] ], + [ "bisque", [ 255, 228, 196 ] ], + [ "blanchedalmond", [ 255, 235, 205 ] ], + [ "blueviolet", [ 138, 43, 226 ] ], + [ "brown", [ 165, 42, 42 ] ], + [ "burlywood", [ 222, 184, 35 ] ], + [ "cadetblue", [ 95, 158, 160 ] ], + [ "chartreuse", [ 127, 255, 0 ] ], + [ "chocolate", [ 210, 105, 30 ] ], + [ "coral", [ 255, 127, 80 ] ], + [ "cornflowerblue", [ 100, 149, 237 ] ], + [ "cornsilk", [ 255, 248, 220 ] ], + [ "crimson", [ 220, 20, 60 ] ], + [ "darkblue", [ 0, 0, 139 ] ], + [ "darkcyan", [ 0, 139, 139 ] ], + [ "darkgoldenrod", [ 184, 134, 11 ] ], + [ "darkgray[*]", [ 169, 169, 169 ] ], + [ "darkgreen", [ 0, 100, 0 ] ], + [ "darkgrey[*]", [ 169, 169, 169 ] ], + [ "darkkhaki", [ 189, 183, 107 ] ], + [ "darkmagenta", [ 139, 0, 139 ] ], + [ "darkolivegreen", [ 85, 107, 47 ] ], + [ "darkorange", [ 255, 140, 0 ] ], + [ "darkorchid", [ 153, 50, 204 ] ], + [ "darkred", [ 139, 0, 0 ] ], + [ "darksalmon", [ 233, 150, 122 ] ], + [ "darkseagreen", [ 143, 188, 143 ] ], + [ "darkslateblue", [ 72, 61, 139 ] ], + [ "darkslategray", [ 47, 79, 79 ] ], + [ "darkslategrey", [ 47, 79, 79 ] ], + [ "darkturquoise", [ 0, 206, 209 ] ], + [ "darkviolet", [ 148, 0, 211 ] ], + [ "deeppink", [ 255, 20, 147 ] ], + [ "deepskyblue", [ 0, 191, 255 ] ], + [ "dimgray", [ 105, 105, 105 ] ], + [ "dimgrey", [ 105, 105, 105 ] ], + [ "dodgerblue", [ 30, 144, 255 ] ], + [ "firebrick", [ 178, 34, 34 ] ], + [ "floralwhite", [ 255, 250, 240 ] ], + [ "forestgreen", [ 34, 139, 34 ] ], + [ "gainsboro", [ 220, 220, 220 ] ], + [ "ghostwhite", [ 248, 248, 255 ] ], + [ "gold", [ 255, 215, 0 ] ], + [ "goldenrod", [ 218, 165, 32 ] ], + [ "greenyellow", [ 173, 255, 47 ] ], + [ "grey", [ 128, 128, 128 ] ], + [ "honeydew", [ 240, 255, 240 ] ], + [ "hotpink", [ 255, 105, 180 ] ], + [ "indianred", [ 205, 92, 92 ] ], + [ "indigo", [ 75, 0, 130 ] ], + [ "ivory", [ 255, 255, 240 ] ], + [ "khaki", [ 240, 230, 140 ] ], + [ "lavender", [ 230, 230, 250 ] ], + [ "lavenderblush", [ 255, 240, 245 ] ], + [ "lawngreen", [ 124, 252, 0 ] ], + [ "lemonchiffon", [ 255, 250, 205 ] ], + [ "lightblue", [ 173, 216, 230 ] ], + [ "lightcoral", [ 240, 128, 128 ] ], + [ "lightcyan", [ 224, 255, 255 ] ], + [ "lightgoldenrodyellow", [ 250, 250, 210 ] ], + [ "lightgray", [ 211, 211, 211 ] ], + [ "lightgreen", [ 144, 238, 144 ] ], + [ "lightgrey", [ 211, 211, 211 ] ], + [ "lightpink", [ 255, 182, 193 ] ], + [ "lightsalmon", [ 255, 160, 122 ] ], + [ "lightseagreen", [ 32, 178, 170 ] ], + [ "lightskyblue", [ 135, 206, 250 ] ], + [ "lightslategray", [ 119, 136, 153 ] ], + [ "lightslategrey", [ 119, 136, 153 ] ], + [ "lightsteelblue", [ 176, 196, 222 ] ], + [ "lightyellow", [ 255, 255, 224 ] ], + [ "limegreen", [ 50, 205, 50 ] ], + [ "linen", [ 250, 240, 230 ] ], + [ "mediumaquamarine", [ 102, 205, 170 ] ], + [ "mediumblue", [ 0, 0, 205 ] ], + [ "mediumorchid", [ 186, 85, 211 ] ], + [ "mediumpurple", [ 147, 112, 219 ] ], + [ "mediumseagreen", [ 60, 179, 113 ] ], + [ "mediumslateblue", [ 123, 104, 238 ] ], + [ "mediumspringgreen", [ 0, 250, 154 ] ], + [ "mediumturquoise", [ 72, 209, 204 ] ], + [ "mediumvioletred", [ 199, 21, 133 ] ], + [ "midnightblue", [ 25, 25, 112 ] ], + [ "mintcream", [ 245, 255, 250 ] ], + [ "mistyrose", [ 255, 228, 225 ] ], + [ "moccasin", [ 255, 228, 181 ] ], + [ "navajowhite", [ 255, 222, 173 ] ], + [ "oldlace", [ 253, 245, 230 ] ], + [ "olivedrab", [ 107, 142, 35 ] ], + [ "orangered", [ 255, 69, 0 ] ], + [ "orchid", [ 218, 112, 214 ] ], + [ "palegoldenrod", [ 238, 232, 170 ] ], + [ "palegreen", [ 152, 251, 152 ] ], + [ "paleturquoise", [ 175, 238, 238 ] ], + [ "palevioletred", [ 219, 112, 147 ] ], + [ "papayawhip", [ 255, 239, 213 ] ], + [ "peachpuff", [ 255, 218, 185 ] ], + [ "peru", [ 205, 133, 63 ] ], + [ "pink", [ 255, 192, 203 ] ], + [ "plum", [ 221, 160, 221 ] ], + [ "powderblue", [ 176, 224, 230 ] ], + [ "rosybrown", [ 188, 143, 143 ] ], + [ "royalblue", [ 65, 105, 225 ] ], + [ "saddlebrown", [ 139, 69, 19 ] ], + [ "salmon", [ 250, 128, 114 ] ], + [ "sandybrown", [ 244, 164, 96 ] ], + [ "seagreen", [ 46, 139, 87 ] ], + [ "seashell", [ 255, 245, 238 ] ], + [ "sienna", [ 160, 82, 45 ] ], + [ "skyblue", [ 135, 206, 235 ] ], + [ "slateblue", [ 106, 90, 205 ] ], + [ "slategray", [ 112, 128, 144 ] ], + [ "slategrey", [ 112, 128, 144 ] ], + [ "snow", [ 255, 250, 250 ] ], + [ "springgreen", [ 0, 255, 127 ] ], + [ "steelblue", [ 70, 130, 180 ] ], + [ "tan", [ 210, 180, 140 ] ], + [ "thistle", [ 216, 191, 216 ] ], + [ "tomato", [ 255, 99, 71 ] ], + [ "turquoise", [ 64, 224, 208 ] ], + [ "violet", [ 238, 130, 238 ] ], + [ "wheat", [ 245, 222, 179 ] ], + [ "whitesmoke", [ 245, 245, 245 ] ], + [ "yellowgreen", [ 154, 205, 50 ] ] + ].forEach(function (value) { + cssToRGB.set(value[0], value[1]); + }); + + /** + * A color manipulation object. + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Float32Array|Number} [r=0] red component or array of color components + * @param {Number} [g=0] green component + * @param {Number} [b=0] blue component + * @param {Number} [alpha=1.0] alpha value + */ + me.Color = me.Object.extend( + /** @scope me.Color.prototype */ + { + + /** + * @ignore + */ + init : function (r, g, b, alpha) { + + /** + * Color components in a Float32Array suitable for WebGL + * @name glArray + * @memberOf me.Color + * @type {Float32Array} + * @readonly + */ + if (typeof (this.glArray) === "undefined") { + this.glArray = new Float32Array([ 0.0, 0.0, 0.0, 1.0 ]); + } + + return this.setColor(r, g, b, alpha); + }, + + /** + * Set this color to the specified value. + * @name setColor + * @memberOf me.Color + * @function + * @param {Number} r red component [0 .. 255] + * @param {Number} g green component [0 .. 255] + * @param {Number} b blue component [0 .. 255] + * @param {Number} [alpha=1.0] alpha value [0.0 .. 1.0] + * @return {me.Color} Reference to this object for method chaining + */ + setColor : function (r, g, b, alpha) { + // Private initialization: copy Color value directly + if (r instanceof me.Color) { + this.glArray.set(r.glArray); + return r; + } + this.r = r; + this.g = g; + this.b = b; + this.alpha = alpha; + return this; + }, + + /** + * Create a new copy of this color object. + * @name clone + * @memberOf me.Color + * @function + * @return {me.Color} Reference to the newly cloned object + */ + clone : function () { + return me.pool.pull("me.Color", this); + }, + + /** + * Copy a color object or CSS color into this one. + * @name copy + * @memberOf me.Color + * @function + * @param {me.Color|String} color + * @return {me.Color} Reference to this object for method chaining + */ + copy : function (color) { + if (color instanceof me.Color) { + this.glArray.set(color.glArray); + return this; + } + + return this.parseCSS(color); + }, + + /** + * Blend this color with the given one using addition. + * @name add + * @memberOf me.Color + * @function + * @param {me.Color} color + * @return {me.Color} Reference to this object for method chaining + */ + add : function (color) { + this.glArray[0] = (this.glArray[0] + color.glArray[0]).clamp(0, 1); + this.glArray[1] = (this.glArray[1] + color.glArray[1]).clamp(0, 1); + this.glArray[2] = (this.glArray[2] + color.glArray[2]).clamp(0, 1); + this.glArray[3] = (this.glArray[3] + color.glArray[3]) / 2; + + return this; + }, + + /** + * Darken this color value by 0..1 + * @name darken + * @memberOf me.Color + * @function + * @param {Number} scale + * @return {me.Color} Reference to this object for method chaining + */ + darken : function (scale) { + scale = scale.clamp(0, 1); + this.glArray[0] *= scale; + this.glArray[1] *= scale; + this.glArray[2] *= scale; + + return this; + }, + + /** + * Lighten this color value by 0..1 + * @name lighten + * @memberOf me.Color + * @function + * @param {Number} scale + * @return {me.Color} Reference to this object for method chaining + */ + lighten : function (scale) { + scale = scale.clamp(0, 1); + this.glArray[0] = (this.glArray[0] + (1 - this.glArray[0]) * scale).clamp(0, 1); + this.glArray[1] = (this.glArray[1] + (1 - this.glArray[1]) * scale).clamp(0, 1); + this.glArray[2] = (this.glArray[2] + (1 - this.glArray[2]) * scale).clamp(0, 1); + + return this; + }, + + /** + * Generate random r,g,b values for this color object + * @name random + * @memberOf me.Color + * @function + * @return {me.Color} Reference to this object for method chaining + */ + random : function () { + return this.setColor( + Math.random() * 256, + Math.random() * 256, + Math.random() * 256, + this.alpha + ); + }, + + /** + * Return true if the r,g,b,a values of this color are equal with the + * given one. + * @name equals + * @memberOf me.Color + * @function + * @param {me.Color} color + * @return {Boolean} + */ + equals : function (color) { + return ( + (this.glArray[0] === color.glArray[0]) && + (this.glArray[1] === color.glArray[1]) && + (this.glArray[2] === color.glArray[2]) && + (this.glArray[3] === color.glArray[3]) + ); + }, + + /** + * Parse a CSS color string and set this color to the corresponding + * r,g,b values + * @name parseCSS + * @memberOf me.Color + * @function + * @param {String} color + * @return {me.Color} Reference to this object for method chaining + */ + parseCSS : function (cssColor) { + // TODO : Memoize this function by caching its input + + if (cssToRGB.has(cssColor)) { + return this.setColor.apply(this, cssToRGB.get(cssColor)); + } + + return this.parseRGB(cssColor); + }, + + /** + * Parse an RGB or RGBA CSS color string + * @name parseRGB + * @memberOf me.Color + * @function + * @param {String} color + * @return {me.Color} Reference to this object for method chaining + */ + parseRGB : function (rgbColor) { + // TODO : Memoize this function by caching its input + + var match = rgbaRx.exec(rgbColor); + if (match) { + return this.setColor(+match[1], +match[2], +match[3], +match[5]); + } + + return this.parseHex(rgbColor); + }, + + /** + * Parse a Hex color ("#RGB", "#RGBA" or "#RRGGBB", "#RRGGBBAA" format) and set this color to + * the corresponding r,g,b,a values + * @name parseHex + * @memberOf me.Color + * @function + * @param {String} color + * @return {me.Color} Reference to this object for method chaining + */ + parseHex : function (hexColor) { + // TODO : Memoize this function by caching its input + + var match; + if ((match = hex8Rx.exec(hexColor))) { + // #AARRGGBB + return this.setColor( + parseInt(match[1], 16), + parseInt(match[2], 16), + parseInt(match[3], 16), + (parseInt(match[4], 16).clamp(0, 255) / 255.0).toFixed(1) + ); + } + + if ((match = hex6Rx.exec(hexColor))) { + // #RRGGBB + return this.setColor( + parseInt(match[1], 16), + parseInt(match[2], 16), + parseInt(match[3], 16) + ); + } + + if ((match = hex4Rx.exec(hexColor))) { + // #ARGB + return this.setColor( + parseInt(match[1] + match[1], 16), + parseInt(match[2] + match[2], 16), + parseInt(match[3] + match[3], 16), + (parseInt(match[4] + match[4], 16).clamp(0, 255) / 255.0).toFixed(1) + ); + } + + if ((match = hex3Rx.exec(hexColor))) { + // #RGB + return this.setColor( + parseInt(match[1] + match[1], 16), + parseInt(match[2] + match[2], 16), + parseInt(match[3] + match[3], 16) + ); + } + + throw new me.Color.Error( + "invalid parameter: " + hexColor + ); + }, + + /** + * Returns the private glArray + * @ignore + */ + toGL : function () { + return this.glArray; + }, + + /** + * Get the color in "#RRGGBB" format + * @name toHex + * @memberOf me.Color + * @function + * @return {String} + */ + toHex : function () { + // TODO : Memoize this function by caching its result until any of + // the r,g,b,a values are changed + + return "#" + this.r.toHex() + this.g.toHex() + this.b.toHex(); + }, + + /** + * Get the color in "#RRGGBBAA" format + * @name toHex8 + * @memberOf me.Color + * @function + * @return {String} + */ + toHex8 : function () { + // TODO : Memoize this function by caching its result until any of + // the r,g,b,a values are changed + + return "#" + this.r.toHex() + this.g.toHex() + this.b.toHex() + this.alpha.toHex(); + }, + + /** + * Get the color in "rgb(R,G,B)" format + * @name toRGB + * @memberOf me.Color + * @function + * @return {String} + */ + toRGB : function () { + // TODO : Memoize this function by caching its result until any of + // the r,g,b,a values are changed + + return "rgb(" + + this.r + "," + + this.g + "," + + this.b + + ")"; + }, + + /** + * Get the color in "rgba(R,G,B,A)" format + * @name toRGBA + * @memberOf me.Color + * @function + * @return {String} + */ + toRGBA : function () { + // TODO : Memoize this function by caching its result until any of + // the r,g,b,a values are changed + + return "rgba(" + + this.r + "," + + this.g + "," + + this.b + "," + + this.alpha + + ")"; + } + }); + + /** + * Color Red Component + * @type Number + * @name r + * @readonly + * @memberOf me.Color + */ + Object.defineProperty(me.Color.prototype, "r", { + /** + * @ignore + */ + get : function () { return ~~(this.glArray[0] * 255); }, + /** + * @ignore + */ + set : function (value) { this.glArray[0] = (~~value || 0).clamp(0, 255) / 255.0; }, + enumerable : true, + configurable : true + }); + + /** + * Color Green Component + * @type Number + * @name g + * @readonly + * @memberOf me.Color + */ + Object.defineProperty(me.Color.prototype, "g", { + /** + * @ignore + */ + get : function () { return ~~(this.glArray[1] * 255); }, + /** + * @ignore + */ + set : function (value) { this.glArray[1] = (~~value || 0).clamp(0, 255) / 255.0; }, + enumerable : true, + configurable : true + }); + + /** + * Color Blue Component + * @type Number + * @name b + * @readonly + * @memberOf me.Color + */ + Object.defineProperty(me.Color.prototype, "b", { + /** + * @ignore + */ + get : function () { return ~~(this.glArray[2] * 255); }, + /** + * @ignore + */ + set : function (value) { this.glArray[2] = (~~value || 0).clamp(0, 255) / 255.0; }, + enumerable : true, + configurable : true + }); + + /** + * Color Alpha Component + * @type Number + * @name alpha + * @readonly + * @memberOf me.Color + */ + Object.defineProperty(me.Color.prototype, "alpha", { + /** + * @ignore + */ + get : function () { return this.glArray[3]; }, + /** + * @ignore + */ + set : function (value) { this.glArray[3] = typeof(value) === "undefined" ? 1.0 : (+value).clamp(0, 1); }, + enumerable : true, + configurable : true + }); + + /** + * Base class for me.Color exception handling. + * @name Error + * @class + * @memberOf me.Color + * @constructor + * @param {String} msg Error message. + */ + me.Color.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Color.Error"; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * A singleton object to access the device localStorage area + * @example + * // Initialize "score" and "lives" with default values + * // This loads the properties from localStorage if they exist, else it sets the given defaults + * me.save.add({ score : 0, lives : 3 }); + * + * // Print all + * // On first load, this prints { score : 0, lives : 3 } + * // On further reloads, it prints { score : 31337, lives : 3, complexObject : ... } + * // Because the following changes will be saved to localStorage + * console.log(JSON.stringify(me.save)); + * + * // Save score + * me.save.score = 31337; + * + * // Also supports complex objects thanks to the JSON backend + * me.save.add({ complexObject : {} }) + * me.save.complexObject = { a : "b", c : [ 1, 2, 3, "d" ], e : { f : [{}] } }; + * + * // WARNING: Do not set any child properties of complex objects directly! + * // Changes made that way will not save. Always set the entire object value at once. + * // If you cannot live with this limitation, there's a workaround: + * me.save.complexObject.c.push("foo"); // Modify a child property + * me.save.complexObject = me.save.complexObject; // Save the entire object! + * + * // Remove "lives" from localStorage + * me.save.remove("lives"); + * @namespace me.save + * @memberOf me + */ + me.save = (function () { + // Variable to hold the object data + var data = {}; + + // a function to check if the given key is a reserved word + function isReserved(key) { + return (key === "add" || key === "remove"); + } + + // Public API + var api = { + /** + * @ignore + */ + _init: function () { + // Load previous data if local Storage is supported + if (me.device.localStorage === true) { + var keys = JSON.parse(localStorage.getItem("me.save")) || []; + keys.forEach(function (key) { + data[key] = JSON.parse(localStorage.getItem("me.save." + key)); + }); + } + }, + + /** + * Add new keys to localStorage and set them to the given default values if they do not exist + * @name add + * @memberOf me.save + * @function + * @param {Object} props key and corresponding values + * @example + * // Initialize "score" and "lives" with default values + * me.save.add({ score : 0, lives : 3 }); + */ + add : function (props) { + Object.keys(props).forEach(function (key) { + if (isReserved(key)) { + return; + } + + (function (prop) { + Object.defineProperty(api, prop, { + configurable : true, + enumerable : true, + /** + * @ignore + */ + get : function () { + return data[prop]; + }, + /** + * @ignore + */ + set : function (value) { + data[prop] = value; + if (me.device.localStorage === true) { + localStorage.setItem("me.save." + prop, JSON.stringify(value)); + } + } + }); + })(key); + + // Set default value for key + if (!(key in data)) { + api[key] = props[key]; + } + }); + + // Save keys + if (me.device.localStorage === true) { + localStorage.setItem("me.save", JSON.stringify(Object.keys(data))); + } + }, + + /** + * Remove a key from localStorage + * @name remove + * @memberOf me.save + * @function + * @param {String} key key to be removed + * @example + * // Remove the "score" key from localStorage + * me.save.remove("score"); + */ + remove : function (key) { + if (!isReserved(key)) { + if (typeof data[key] !== "undefined") { + delete data[key]; + if (me.device.localStorage === true) { + localStorage.removeItem("me.save." + key); + localStorage.setItem("me.save", JSON.stringify(Object.keys(data))); + } + } + } + } + }; + + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tile QT 0.7.x format + * http://www.mapeditor.org/ + * + */ +(function () { + /** + * a collection of TMX utility Function + * @final + * @memberOf me + * @ignore + */ + me.TMXUtils = (function () { + /* + * PUBLIC + */ + + // hold public stuff in our singleton + var api = {}; + + /** + * set and interpret a TMX property value + * @ignore + */ + function setTMXValue(name, type, value) { + var match; + + if (typeof(value) !== "string") { + // Value is already normalized (e.g. with JSON maps) + return value; + } + + switch (type) { + + case "int" : + case "float" : + value = Number(value); + break; + + case "bool" : + value = (value === "true"); + break; + + default : + // try to parse it anyway + if (!value || value.isBoolean()) { + // if value not defined or boolean + value = value ? (value === "true") : true; + } + else if (value.isNumeric()) { + // check if numeric + value = Number(value); + } + else if (value.search(/^json:/i) === 0) { + // try to parse it + match = value.split(/^json:/i)[1]; + try { + value = JSON.parse(match); + } + catch (e) { + throw new me.Error("Unable to parse JSON: " + match); + } + } + else if (value.search(/^eval:/i) === 0) { + // try to evaluate it + match = value.split(/^eval:/i)[1]; + try { + // eslint-disable-next-line + value = eval(match); + } + catch (e) { + throw new me.Error("Unable to evaluate: " + match); + } + } + else if ( + ((match = value.match(/^#([\da-fA-F])([\da-fA-F]{3})$/))) || + ((match = value.match(/^#([\da-fA-F]{2})([\da-fA-F]{6})$/))) + ) { + value = "#" + match[2] + match[1]; + } + + // normalize values + if (name.search(/^(ratio|anchorPoint)$/) === 0) { + // convert number to vector + if (typeof(value) === "number") { + value = { + "x" : value, + "y" : value + }; + } + } + } + // return the interpreted value + return value; + } + + function parseAttributes(obj, elt) { + // do attributes + if (elt.attributes && elt.attributes.length > 0) { + for (var j = 0; j < elt.attributes.length; j++) { + var attribute = elt.attributes.item(j); + if (typeof(attribute.name) !== "undefined") { + // DOM4 (Attr no longer inherit from Node) + obj[attribute.name] = attribute.value; + } else { + // else use the deprecated ones + obj[attribute.nodeName] = attribute.nodeValue; + } + } + } + } + + /** + * Decode the given data + * @ignore + */ + api.decode = function (data, encoding, compression) { + compression = compression || "none"; + encoding = encoding || "none"; + + switch (encoding) { + case "csv": + return me.utils.decodeCSV(data); + + case "base64": + var decoded = me.utils.decodeBase64AsArray(data, 4); + return ( + (compression === "none") ? + decoded : + me.utils.decompress(decoded, compression) + ); + + case "none": + return data; + + case "xml": + throw new me.Error("XML encoding is deprecated, use base64 instead"); + + default: + throw new me.Error("Unknown layer encoding: " + encoding); + } + }; + + /** + * Normalize TMX format to Tiled JSON format + * @ignore + */ + api.normalize = function (obj, item) { + var nodeName = item.nodeName; + + switch (nodeName) { + case "data": + var data = api.parse(item); + // When no encoding is given, the tiles are stored as individual XML tile elements. + data.encoding = data.encoding || "xml"; + obj.data = api.decode(data.text, data.encoding, data.compression); + obj.encoding = "none"; + break; + + case "imagelayer": + case "layer": + case "objectgroup": + var layer = api.parse(item); + layer.type = (nodeName === "layer" ? "tilelayer" : nodeName); + if (layer.image) { + layer.image = layer.image.source; + } + + obj.layers = obj.layers || []; + obj.layers.push(layer); + break; + + case "animation": + obj.animation = api.parse(item).frames; + break; + + case "frame": + case "object": + var name = nodeName + "s"; + obj[name] = obj[name] || []; + obj[name].push(api.parse(item)); + break; + + case "tile": + var tile = api.parse(item); + obj.tiles = obj.tiles || {}; + obj.tiles[tile.id] = tile; + break; + + case "tileset": + var tileset = api.parse(item); + if (tileset.image) { + tileset.imagewidth = tileset.image.width; + tileset.imageheight = tileset.image.height; + tileset.image = tileset.image.source; + } + + obj.tilesets = obj.tilesets || []; + obj.tilesets.push(tileset); + break; + + case "polygon": + case "polyline": + obj[nodeName] = []; + + // Get a point array + var points = api.parse(item).points.split(" "); + + // And normalize them into an array of vectors + for (var i = 0, v; i < points.length; i++) { + v = points[i].split(","); + obj[nodeName].push({ + "x" : +v[0], + "y" : +v[1] + }); + } + + break; + + case "properties": + obj.properties = api.parse(item); + break; + + case "property": + var property = api.parse(item); + obj[property.name] = setTMXValue( + property.name, + // in XML type is undefined for "string" values + property.type || "string", + property.value + ); + break; + + default: + obj[nodeName] = api.parse(item); + break; + } + }; + + /** + * Parse a XML TMX object and returns the corresponding javascript object + * @ignore + */ + api.parse = function (xml) { + // Create the return object + var obj = {}; + + var text = ""; + + if (xml.nodeType === 1) { + // do attributes + parseAttributes(obj, xml); + } + + // do children + if (xml.hasChildNodes()) { + for (var i = 0; i < xml.childNodes.length; i++) { + var item = xml.childNodes.item(i); + + switch (item.nodeType) { + case 1: + api.normalize(obj, item); + break; + + case 3: + text += item.nodeValue.trim(); + break; + } + } + } + + if (text) { + obj.text = text; + } + + return obj; + }; + + /** + * Apply TMX Properties to the given object + * @ignore + */ + api.applyTMXProperties = function (obj, data) { + var properties = data.properties; + var types = data.propertytypes; + if (typeof(properties) !== "undefined") { + for (var name in properties) { + if (properties.hasOwnProperty(name)) { + var type = "string"; + if (typeof(types) !== "undefined") { + type = types[name]; + } + // set the value + obj[name] = setTMXValue(name, type, properties[name]); + } + } + } + }; + + // return our object + return api; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tile QT 0.7.x format + * http://www.mapeditor.org/ + * + */ +(function () { + + /** + * TMX Object Group
+ * contains the object group definition as defined in Tiled.
+ * note : object group definition is translated into the virtual `me.game.world` using `me.Container`. + * @see me.Container + * @class + * @extends me.Object + * @memberOf me + * @constructor + */ + me.TMXObjectGroup = me.Object.extend({ + /** + * @ignore + */ + init : function (map, data, z) { + + /** + * group name + * @public + * @type String + * @name name + * @memberOf me.TMXObjectGroup + */ + this.name = data.name; + + /** + * group width + * @public + * @type Number + * @name width + * @memberOf me.TMXObjectGroup + */ + this.width = data.width; + + /** + * group height + * @public + * @type Number + * @name height + * @memberOf me.TMXObjectGroup + */ + this.height = data.height; + + /** + * group z order + * @public + * @type Number + * @name z + * @memberOf me.TMXObjectGroup + */ + this.z = z; + + /** + * group objects list definition + * @see me.TMXObject + * @public + * @type Array + * @name name + * @memberOf me.TMXObjectGroup + */ + this.objects = []; + + var visible = typeof(data.visible) !== "undefined" ? data.visible : true; + this.opacity = (visible === true) ? (+data.opacity || 1.0).clamp(0.0, 1.0) : 0; + + // check if we have any user-defined properties + me.TMXUtils.applyTMXProperties(this, data); + + // parse all objects + var _objects = data.objects; + if (_objects) { + var self = this; + _objects.forEach(function (tmxObj) { + //self.objects.push(new me.TMXObject(tmxObj, map.orientation, map.tilesets, z)); + self.objects.push(new me.TMXObject(map, tmxObj, z)); + }); + } + }, + + /** + * reset function + * @ignore + * @function + */ + + destroy : function () { + // clear all allocated objects + this.objects = null; + }, + + /** + * return the object count + * @ignore + * @function + */ + getObjectCount : function () { + return this.objects.length; + }, + + /** + * returns the object at the specified index + * @ignore + * @function + */ + getObjectByIndex : function (idx) { + return this.objects[idx]; + } + }); + + /** + * a TMX Object defintion, as defined in Tiled.
+ * note : object definition are translated into the virtual `me.game.world` using `me.Entity`. + * @see me.Entity + * @class + * @extends Object + * @memberOf me + * @constructor + */ + me.TMXObject = me.Object.extend({ + /** + * @ignore + */ + init : function (map, tmxObj, z) { + + /** + * object point list (for Polygon and PolyLine) + * @public + * @type Vector2d[] + * @name points + * @memberOf me.TMXObject + */ + this.points = undefined; + + /** + * object name + * @public + * @type String + * @name name + * @memberOf me.TMXObject + */ + this.name = tmxObj.name; + + /** + * object x position + * @public + * @type Number + * @name x + * @memberOf me.TMXObject + */ + this.x = +tmxObj.x; + + /** + * object y position + * @public + * @type Number + * @name y + * @memberOf me.TMXObject + */ + this.y = +tmxObj.y; + + /** + * object z order + * @public + * @type Number + * @name z + * @memberOf me.TMXObject + */ + this.z = +z; + + /** + * object width + * @public + * @type Number + * @name width + * @memberOf me.TMXObject + */ + this.width = +tmxObj.width || 0; + + /** + * object height + * @public + * @type Number + * @name height + * @memberOf me.TMXObject + */ + this.height = +tmxObj.height || 0; + + /** + * object gid value + * when defined the object is a tiled object + * @public + * @type Number + * @name gid + * @memberOf me.TMXObject + */ + this.gid = +tmxObj.gid || null; + + /** + * object type + * @public + * @type String + * @name type + * @memberOf me.TMXObject + */ + this.type = tmxObj.type; + + /** + * The rotation of the object in radians clockwise (defaults to 0) + * @public + * @type Number + * @name rotation + * @memberOf me.TMXObject + */ + this.rotation = Number.prototype.degToRad(+tmxObj.rotation || 0); + + /** + * object unique identifier per level (Tiled 0.11.x+) + * @public + * @type Number + * @name id + * @memberOf me.TMXObject + */ + this.id = +tmxObj.id || undefined; + + /** + * object orientation (orthogonal or isometric) + * @public + * @type String + * @name orientation + * @memberOf me.TMXObject + */ + this.orientation = map.orientation; + + /** + * the collision shapes defined for this object + * @public + * @type Array + * @name shapes + * @memberOf me.TMXObject + */ + this.shapes = undefined; + + /** + * if true, the object is an Ellipse + * @public + * @type Boolean + * @name isEllipse + * @memberOf me.TMXObject + */ + this.isEllipse = false; + + /** + * if true, the object is a Polygon + * @public + * @type Boolean + * @name isPolygon + * @memberOf me.TMXObject + */ + this.isPolygon = false; + + /** + * if true, the object is a PolyLine + * @public + * @type Boolean + * @name isPolyLine + * @memberOf me.TMXObject + */ + this.isPolyLine = false; + + // check if the object has an associated gid + if (typeof this.gid === "number") { + this.setTile(map.tilesets); + } + else { + if (typeof(tmxObj.ellipse) !== "undefined") { + this.isEllipse = true; + } + else { + var points = tmxObj.polygon; + if (typeof(points) !== "undefined") { + this.isPolygon = true; + } + else { + points = tmxObj.polyline; + if (typeof(points) !== "undefined") { + this.isPolyLine = true; + } + } + if (typeof(points) !== "undefined") { + this.points = []; + var self = this; + points.forEach(function (point) { + self.points.push(new me.Vector2d(point.x, point.y)); + }); + } + } + } + + // Adjust the Position to match Tiled + map.getRenderer().adjustPosition(this); + + // set the object properties + me.TMXUtils.applyTMXProperties(this, tmxObj); + + // define the object shapes if required + if (!this.shapes) { + this.shapes = this.parseTMXShapes(); + } + }, + + /** + * set the object image (for Tiled Object) + * @ignore + * @function + */ + setTile : function (tilesets) { + // get the corresponding tileset + var tileset = tilesets.getTilesetByGid(this.gid); + + // set width and height equal to tile size + this.width = this.framewidth = tileset.tilewidth; + this.height = this.frameheight = tileset.tileheight; + + // the object corresponding tile object + this.tile = new me.Tile(this.x, this.y, this.gid, tileset); + }, + + /** + * parses the TMX shape definition and returns a corresponding array of me.Shape object + * @name parseTMXShapes + * @memberOf me.TMXObject + * @private + * @function + * @return {me.Polygon[]|me.Line[]|me.Ellipse[]} an array of shape objects + */ + parseTMXShapes : function () { + var i = 0; + var shapes = []; + + // add an ellipse shape + if (this.isEllipse === true) { + // ellipse coordinates are the center position, so set default to the corresonding radius + shapes.push((new me.Ellipse( + this.width / 2, + this.height / 2, + this.width, + this.height + )).rotate(this.rotation)); + } + + // add a polygon + else if (this.isPolygon === true) { + shapes.push((new me.Polygon(0, 0, this.points)).rotate(this.rotation)); + } + + // add a polyline + else if (this.isPolyLine === true) { + var p = this.points; + var p1, p2; + var segments = p.length - 1; + for (i = 0; i < segments; i++) { + // clone the value before, as [i + 1] + // is reused later by the next segment + p1 = p[i]; + p2 = p[i + 1].clone(); + if (this.rotation !== 0) { + p1 = p1.rotate(this.rotation); + p2 = p2.rotate(this.rotation); + } + shapes.push(new me.Line(0, 0, [ p1, p2 ])); + } + } + + // it's a rectangle, returns a polygon object anyway + else { + shapes.push((new me.Polygon( + 0, 0, [ + new me.Vector2d(), new me.Vector2d(this.width, 0), + new me.Vector2d(this.width, this.height), new me.Vector2d(0, this.height) + ] + )).rotate(this.rotation)); + } + + // Apply isometric projection + if (this.orientation === "isometric") { + for (i = 0; i < shapes.length; i++) { + shapes[i].toIso(); + } + } + + return shapes; + }, + /** + * getObjectPropertyByName + * @ignore + * @function + */ + getObjectPropertyByName : function (name) { + return this[name]; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tile QT 0.7.x format + * http://www.mapeditor.org/ + * + */ +(function () { + + // bitmask constants to check for flipped & rotated tiles + var TMX_FLIP_H = 0x80000000, + TMX_FLIP_V = 0x40000000, + TMX_FLIP_AD = 0x20000000, + TMX_CLEAR_BIT_MASK = ~(0x80000000 | 0x40000000 | 0x20000000); + + /** + * a basic tile object + * @class + * @extends me.Rect + * @memberOf me + * @constructor + * @param {Number} x x index of the Tile in the map + * @param {Number} y y index of the Tile in the map + * @param {Number} gid tile gid + * @param {me.TMXTileset} tileset the corresponding tileset object + + */ + me.Tile = me.Rect.extend({ + /** @ignore */ + init : function (x, y, gid, tileset) { + /** + * tileset + * @public + * @type me.TMXTileset + * @name me.Tile#tileset + */ + this.tileset = tileset; + + /** + * the tile transformation matrix (if defined) + * @ignore + */ + this.currentTransform = null; + + // call the parent constructor + me.Rect.prototype.init.apply(this, [x * tileset.tilewidth, y * tileset.tileheight, tileset.tilewidth, tileset.tileheight]); + + // Tile col / row pos + this.col = x; + this.row = y; + + /** + * tileId + * @public + * @type int + * @name me.Tile#tileId + */ + this.tileId = gid; + /** + * True if the tile is flipped horizontally
+ * @public + * @type Boolean + * @name me.Tile#flipX + */ + this.flippedX = (this.tileId & TMX_FLIP_H) !== 0; + /** + * True if the tile is flipped vertically
+ * @public + * @type Boolean + * @name me.Tile#flippedY + */ + this.flippedY = (this.tileId & TMX_FLIP_V) !== 0; + /** + * True if the tile is flipped anti-diagonally
+ * @public + * @type Boolean + * @name me.Tile#flippedAD + */ + this.flippedAD = (this.tileId & TMX_FLIP_AD) !== 0; + + /** + * Global flag that indicates if the tile is flipped
+ * @public + * @type Boolean + * @name me.Tile#flipped + */ + this.flipped = this.flippedX || this.flippedY || this.flippedAD; + // create a transformation matrix if required + if (this.flipped === true) { + this.createTransform(); + } + + // clear out the flags and set the tileId + this.tileId &= TMX_CLEAR_BIT_MASK; + }, + + /** + * create a transformation matrix for this tile + * @ignore + */ + createTransform : function () { + if (this.currentTransform === null) { + this.currentTransform = new me.Matrix2d(); + } else { + // reset the matrix + this.currentTransform.identity(); + } + + if (this.flippedAD) { + // Use shearing to swap the X/Y axis + this.currentTransform.setTransform( + 0, 1, 0, + 1, 0, 0, + 0, 0, 1 + ); + this.currentTransform.translate(0, this.height - this.width); + } + if (this.flippedX) { + this.currentTransform.translate( + (this.flippedAD ? 0 : this.width), + (this.flippedAD ? this.height : 0) + ); + this.currentTransform.scaleX(-1); + } + if (this.flippedY) { + this.currentTransform.translate( + (this.flippedAD ? this.width : 0), + (this.flippedAD ? 0 : this.height) + ); + this.currentTransform.scaleY(-1); + } + }, + + /** + * return a renderable object for this Tile object + * @name me.Tile#getRenderable + * @public + * @function + * @param {Object} [settings] see {@link me.Sprite} + * @return {me.Renderable} a me.Sprite object + */ + getRenderable : function (settings) { + var renderable; + var tileset = this.tileset; + + if (tileset.animations.has(this.tileId)) { + var frames = []; + var frameId = []; + (tileset.animations.get(this.tileId).frames).forEach(function (frame) { + frameId.push(frame.tileid); + frames.push({ + name : "" + frame.tileid, + delay : frame.duration + }); + }); + renderable = tileset.texture.createAnimationFromName(frameId, settings); + renderable.addAnimation(this.tileId - tileset.firstgid, frames); + renderable.setCurrentAnimation(this.tileId - tileset.firstgid); + + } else { + renderable = tileset.texture.createSpriteFromName(this.tileId - tileset.firstgid, settings); + } + + // any H/V flipping to apply? + if (this.flippedX) { + renderable.currentTransform.scaleX(-1); + } + if (this.flippedY) { + renderable.currentTransform.scaleY(-1); + } + + return renderable; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tile QT 0.7.x format + * http://www.mapeditor.org/ + * + */ +(function () { + + // bitmask constants to check for flipped & rotated tiles + var TMX_CLEAR_BIT_MASK = ~(0x80000000 | 0x40000000 | 0x20000000); + + /** + * a TMX Tile Set Object + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Object} tileset tileset JSON definition + */ + me.TMXTileset = me.Object.extend({ + /** + * constructor + * @ignore + */ + init: function (tileset) { + var i = 0; + // first gid + + // tile properties (collidable, etc..) + this.TileProperties = []; + + this.firstgid = this.lastgid = +tileset.firstgid; + + // check if an external tileset is defined + if (typeof(tileset.source) !== "undefined") { + var src = tileset.source; + var ext = me.utils.getFileExtension(src); + if (ext === "tsx" || ext === "json") { + // load the external tileset (TSX/JSON) + tileset = me.loader.getTMX(me.utils.getBasename(src)); + if (!tileset) { + throw new me.Error(src + " external TSX/JSON tileset not found"); + } + } + } + + this.name = tileset.name; + this.tilewidth = +tileset.tilewidth; + this.tileheight = +tileset.tileheight; + this.spacing = +tileset.spacing || 0; + this.margin = +tileset.margin || 0; + + // set tile offset properties (if any) + this.tileoffset = new me.Vector2d(); + + /** + * Tileset contains animated tiles + * @public + * @type Boolean + * @name me.TMXTileset#isAnimated + */ + this.isAnimated = false; + + /** + * Tileset animations + * @private + * @type Map + * @name me.TMXTileset#animations + */ + this.animations = new Map(); + + /** + * Remember the last update timestamp to prevent too many animation updates + * @private + * @type Map + * @name me.TMXTileset#_lastUpdate + */ + this._lastUpdate = 0; + + var tiles = tileset.tiles; + for (i in tiles) { + if (tiles.hasOwnProperty(i)) { + if ("animation" in tiles[i]) { + this.isAnimated = true; + this.animations.set(+i + this.firstgid, { + dt : 0, + idx : 0, + frames : tiles[i].animation, + cur : tiles[i].animation[0] + }); + } + // set tile properties, if any (XML format) + if ("properties" in tiles[i]) { + this.setTileProperty(+i + this.firstgid, tiles[i].properties); + } + } + } + + var offset = tileset.tileoffset; + if (offset) { + this.tileoffset.x = +offset.x; + this.tileoffset.y = +offset.y; + } + + // set tile properties, if any (JSON format) + var tileInfo = tileset.tileproperties; + if (tileInfo) { + for (i in tileInfo) { + if (tileInfo.hasOwnProperty(i)) { + this.setTileProperty(+i + this.firstgid, tileInfo[i]); + } + } + } + + this.image = me.utils.getImage(tileset.image); + if (!this.image) { + throw new me.TMXTileset.Error("melonJS: '" + tileset.image + "' file for tileset '" + this.name + "' not found!"); + } + + // create a texture atlas for the given tileset + this.texture = me.video.renderer.cache.get(this.image, { + framewidth : this.tilewidth, + frameheight : this.tileheight, + margin : this.margin, + spacing : this.spacing + }); + this.atlas = this.texture.getAtlas(); + + // calculate the number of tiles per horizontal line + var hTileCount = +tileset.columns || ~~(this.image.width / (this.tilewidth + this.spacing)); + var vTileCount = ~~(this.image.height / (this.tileheight + this.spacing)); + // compute the last gid value in the tileset + this.lastgid = this.firstgid + (((hTileCount * vTileCount) - 1) || 0); + if (tileset.tilecount && this.lastgid - this.firstgid + 1 !== +tileset.tilecount) { + console.warn( + "Computed tilecount (" + (this.lastgid - this.firstgid + 1) + + ") does not match expected tilecount (" + tileset.tilecount + ")" + ); + } + }, + + /** + * set the tile properties + * @ignore + * @function + */ + setTileProperty : function (gid, prop) { + // set the given tile id + this.TileProperties[gid] = prop; + }, + + /** + * return true if the gid belongs to the tileset + * @name me.TMXTileset#contains + * @public + * @function + * @param {Number} gid + * @return {Boolean} + */ + contains : function (gid) { + return gid >= this.firstgid && gid <= this.lastgid; + }, + + /** + * Get the view (local) tile ID from a GID, with animations applied + * @name me.TMXTileset#getViewTileId + * @public + * @function + * @param {Number} gid Global tile ID + * @return {Number} View tile ID + */ + getViewTileId : function (gid) { + if (this.animations.has(gid)) { + // apply animations + gid = this.animations.get(gid).cur.tileid; + } + else { + // get the local tileset id + gid -= this.firstgid; + } + + return gid; + }, + + /** + * return the properties of the specified tile + * @name me.TMXTileset#getTileProperties + * @public + * @function + * @param {Number} tileId + * @return {Object} + */ + getTileProperties: function (tileId) { + return this.TileProperties[tileId]; + }, + + // update tile animations + update : function (dt) { + var duration = 0, + now = me.timer.getTime(), + result = false; + + if (this._lastUpdate !== now) { + this._lastUpdate = now; + + this.animations.forEach(function (anim) { + anim.dt += dt; + duration = anim.cur.duration; + while (anim.dt >= duration) { + anim.dt -= duration; + anim.idx = (anim.idx + 1) % anim.frames.length; + anim.cur = anim.frames[anim.idx]; + duration = anim.cur.duration; + result = true; + } + }); + } + + return result; + }, + + // draw the x,y tile + drawTile : function (renderer, dx, dy, tmxTile) { + // check if any transformation is required + if (tmxTile.flipped) { + renderer.save(); + // apply the tile current transform + renderer.translate(dx, dy); + renderer.transform(tmxTile.currentTransform); + // reset both values as managed through transform(); + dx = dy = 0; + } + + var offset = this.atlas[this.getViewTileId(tmxTile.tileId)].offset; + + // draw the tile + renderer.drawImage( + this.image, + offset.x, offset.y, + this.tilewidth, this.tileheight, + dx, dy, + this.tilewidth + renderer.uvOffset, this.tileheight + renderer.uvOffset + ); + + if (tmxTile.flipped) { + // restore the context to the previous state + renderer.restore(); + } + } + }); + + /** + * an object containing all tileset + * @class + * @memberOf me + * @constructor + */ + me.TMXTilesetGroup = me.Object.extend({ + /** + * constructor + * @ignore + */ + init: function () { + this.tilesets = []; + this.length = 0; + }, + + /** + * add a tileset to the tileset group + * @name me.TMXTilesetGroup#add + * @public + * @function + * @param {me.TMXTileset} tileset + */ + add : function (tileset) { + this.tilesets.push(tileset); + this.length++; + }, + + /** + * return the tileset at the specified index + * @name me.TMXTilesetGroup#getTilesetByIndex + * @public + * @function + * @param {Number} i + * @return {me.TMXTileset} corresponding tileset + */ + getTilesetByIndex : function (i) { + return this.tilesets[i]; + }, + + /** + * return the tileset corresponding to the specified id
+ * will throw an exception if no matching tileset is found + * @name me.TMXTilesetGroup#getTilesetByGid + * @public + * @function + * @param {Number} gid + * @return {me.TMXTileset} corresponding tileset + */ + getTilesetByGid : function (gid) { + var invalidRange = -1; + + // clear the gid of all flip/rotation flags + gid &= TMX_CLEAR_BIT_MASK; + + // cycle through all tilesets + for (var i = 0, len = this.tilesets.length; i < len; i++) { + // return the corresponding tileset if matching + if (this.tilesets[i].contains(gid)) { + return this.tilesets[i]; + } + // typically indicates a layer with no asset loaded (collision?) + if (this.tilesets[i].firstgid === this.tilesets[i].lastgid && + gid >= this.tilesets[i].firstgid) { + // store the id if the [firstgid .. lastgid] is invalid + invalidRange = i; + } + } + // return the tileset with the invalid range + if (invalidRange !== -1) { + return this.tilesets[invalidRange]; + } + else { + throw new me.Error("no matching tileset found for gid " + gid); + } + } + }); + + /** + * Base class for TMXTileset exception handling. + * @name Error + * @class + * @memberOf me.TMXTileset + * @constructor + * @param {String} msg Error message. + */ + me.TMXTileset.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.TMXTileset.Error"; + } + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tiled (0.7+) format + * http://www.mapeditor.org/ + * + */ +(function () { + + // scope global var & constants + var offsetsStaggerX = [ + {x: 0, y: 0}, + {x: + 1, y: - 1}, + {x: + 1, y: 0}, + {x: + 2, y: 0} + ]; + var offsetsStaggerY = [ + {x: 0, y: 0}, + {x: - 1, y: + 1}, + {x: 0, y: + 1}, + {x: 0, y: + 2} + ]; + + /** + * The map renderer base class + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {Number} cols width of the tilemap in tiles + * @param {Number} rows height of the tilemap in tiles + * @param {Number} tilewidth width of each tile in pixels + * @param {Number} tileheight height of each tile in pixels + */ + me.TMXRenderer = me.Object.extend({ + // constructor + init: function (cols, rows, tilewidth, tileheight) { + this.cols = cols; + this.rows = rows; + this.tilewidth = tilewidth; + this.tileheight = tileheight; + }, + + /** + * return true if the renderer can render the specified layer + * @name me.TMXRenderer#canRender + * @public + * @function + * @param {me.TMXTileMap|me.TMXLayer} component TMX Map or Layer + * @return {boolean} + */ + canRender : function (component) { + return ( + (this.cols === component.cols) && + (this.rows === component.rows) && + (this.tilewidth === component.tilewidth) && + (this.tileheight === component.tileheight) + ); + }, + + /** + * return the tile position corresponding to the specified pixel + * @name me.TMXRenderer#pixelToTileCoords + * @public + * @function + * @param {Number} x X coordinate + * @param {Number} y Y coordinate + * @param {me.Vector2d} [vector] an optional vector object where to put the return values + * @return {me.Vector2d} + */ + pixelToTileCoords : function (x, y, v) { + return v; + }, + + /** + * return the pixel position corresponding of the specified tile + * @name me.TMXRenderer#tileToPixelCoords + * @public + * @function + * @param {Number} col tile horizontal position + * @param {Number} row tile vertical position + * @param {me.Vector2d} [vector] an optional vector object where to put the return values + * @return {me.Vector2d} + */ + tileToPixelCoords : function (x, y, v) { + return v; + }, + + /** + * return the tile position corresponding for the given X coordinate + * @name me.TMXRenderer#pixelToTileX + * @public + * @function + * @param {Number} x X coordinate + * @return {Number} tile vertical position + */ + pixelToTileX : function (x) { + }, + + /** + * return the tile position corresponding for the given Y coordinates + * @name me.TMXRenderer#pixelToTileY + * @public + * @function + * @param {Number} y Y coordinate + * @return {Number} tile horizontal position + */ + pixelToTileY : function (y) { + }, + + /** + * draw the given tile at the specified layer + * @name me.TMXRenderer#drawTile + * @public + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object + * @param {Number} x X coordinate where to draw the tile + * @param {Number} y Y coordinate where to draw the tile + * @param {me.Tile} tile the tile object to draw + */ + drawTile : function (renderer, x, y, tile) { + }, + + /** + * draw the given TMX Layer for the given area + * @name me.TMXRenderer#drawTileLayer + * @public + * @function + * @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object + * @param {me.TMXLayer} layer a TMX Layer object + * @param {me.Rect} rect the area of the layer to draw + */ + drawTileLayer : function (renderer, layer, rect) { + } + + }); + + /** + * an Orthogonal Map Renderder + * @memberOf me + * @extends me.TMXRenderer + * @memberOf me + * @constructor + * @param {Number} cols width of the tilemap in tiles + * @param {Number} rows height of the tilemap in tiles + * @param {Number} tilewidth width of each tile in pixels + * @param {Number} tileheight height of each tile in pixels + */ + me.TMXOrthogonalRenderer = me.TMXRenderer.extend({ + /** + * return true if the renderer can render the specified layer + * @ignore + */ + canRender : function (layer) { + return ( + (layer.orientation === "orthogonal") && + me.TMXRenderer.prototype.canRender.apply(this, [ layer ]) + ); + }, + + /** + * return the tile position corresponding to the specified pixel + * @ignore + */ + pixelToTileCoords : function (x, y, v) { + var ret = v || new me.Vector2d(); + return ret.set( + this.pixelToTileX(x), + this.pixelToTileY(y) + ); + }, + + /** + * return the tile position corresponding for the given X coordinate + * @ignore + */ + pixelToTileX : function (x) { + return x / this.tilewidth; + }, + + /** + * return the tile position corresponding for the given Y coordinates + * @ignore + */ + pixelToTileY : function (y) { + return y / this.tileheight; + }, + + /** + * return the pixel position corresponding of the specified tile + * @ignore + */ + tileToPixelCoords : function (x, y, v) { + var ret = v || new me.Vector2d(); + return ret.set( + x * this.tilewidth, + y * this.tileheight + ); + }, + + /** + * fix the position of Objects to match + * the way Tiled places them + * @ignore + */ + adjustPosition: function (obj) { + // only adjust position if obj.gid is defined + if (typeof(obj.gid) === "number") { + // Tiled objects origin point is "bottom-left" in Tiled, + // "top-left" in melonJS) + obj.y -= obj.height; + } + }, + + /** + * draw the tile map + * @ignore + */ + drawTile : function (renderer, x, y, tmxTile) { + var tileset = tmxTile.tileset; + // draw the tile + tileset.drawTile( + renderer, + tileset.tileoffset.x + x * this.tilewidth, + tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight, + tmxTile + ); + }, + + /** + * draw the tile map + * @ignore + */ + drawTileLayer : function (renderer, layer, rect) { + // get top-left and bottom-right tile position + var start = this.pixelToTileCoords( + Math.max(rect.pos.x - (layer.maxTileSize.width - layer.tilewidth), 0), + Math.max(rect.pos.y - (layer.maxTileSize.height - layer.tileheight), 0), + me.pool.pull("me.Vector2d") + ).floorSelf(); + + var end = this.pixelToTileCoords( + rect.pos.x + rect.width + this.tilewidth, + rect.pos.y + rect.height + this.tileheight, + me.pool.pull("me.Vector2d") + ).ceilSelf(); + + //ensure we are in the valid tile range + end.x = end.x > this.cols ? this.cols : end.x; + end.y = end.y > this.rows ? this.rows : end.y; + + // main drawing loop + for (var y = start.y; y < end.y; y++) { + for (var x = start.x; x < end.x; x++) { + var tmxTile = layer.layerData[x][y]; + if (tmxTile) { + this.drawTile(renderer, x, y, tmxTile); + } + } + } + + me.pool.push(start); + me.pool.push(end); + } + }); + + + /** + * an Isometric Map Renderder + * @memberOf me + * @extends me.TMXRenderer + * @memberOf me + * @constructor + * @param {Number} cols width of the tilemap in tiles + * @param {Number} rows height of the tilemap in tiles + * @param {Number} tilewidth width of each tile in pixels + * @param {Number} tileheight height of each tile in pixels + */ + me.TMXIsometricRenderer = me.TMXRenderer.extend({ + // constructor + init: function (cols, rows, tilewidth, tileheight) { + me.TMXRenderer.prototype.init.apply(this, [ + cols, + rows, + tilewidth, + tileheight + ]); + + this.hTilewidth = tilewidth / 2; + this.hTileheight = tileheight / 2; + this.originX = this.rows * this.hTilewidth; + }, + + /** + * return true if the renderer can render the specified layer + * @ignore + */ + canRender : function (layer) { + return ( + (layer.orientation === "isometric") && + me.TMXRenderer.prototype.canRender.apply(this, [ layer ]) + ); + }, + + /** + * return the tile position corresponding to the specified pixel + * @ignore + */ + pixelToTileCoords : function (x, y, v) { + var ret = v || new me.Vector2d(); + return ret.set( + this.pixelToTileX(x, y), + this.pixelToTileY(y, x) + ); + }, + + /** + * return the tile position corresponding for the given X coordinate + * @ignore + */ + pixelToTileX : function (x, y) { + return (y / this.tileheight) + ((x - this.originX) / this.tilewidth); + }, + + /** + * return the tile position corresponding for the given Y coordinates + * @ignore + */ + pixelToTileY : function (y, x) { + return (y / this.tileheight) - ((x - this.originX) / this.tilewidth); + }, + + /** + * return the pixel position corresponding of the specified tile + * @ignore + */ + tileToPixelCoords : function (x, y, v) { + var ret = v || new me.Vector2d(); + return ret.set( + (x - y) * this.hTilewidth + this.originX, + (x + y) * this.hTileheight + ); + }, + + /** + * fix the position of Objects to match + * the way Tiled places them + * @ignore + */ + adjustPosition: function (obj) { + var tileX = obj.x / this.hTilewidth; + var tileY = obj.y / this.tileheight; + var isoPos = me.pool.pull("me.Vector2d"); + + this.tileToPixelCoords(tileX, tileY, isoPos); + + obj.x = isoPos.x; + obj.y = isoPos.y; + + me.pool.push(isoPos); + }, + + /** + * draw the tile map + * @ignore + */ + drawTile : function (renderer, x, y, tmxTile) { + var tileset = tmxTile.tileset; + // draw the tile + tileset.drawTile( + renderer, + ((this.cols - 1) * tileset.tilewidth + (x - y) * tileset.tilewidth >> 1), + (-tileset.tilewidth + (x + y) * tileset.tileheight >> 2), + tmxTile + ); + }, + + /** + * draw the tile map + * @ignore + */ + drawTileLayer : function (renderer, layer, rect) { + // cache a couple of useful references + var tileset = layer.tileset; + var offset = tileset.tileoffset; + + // get top-left and bottom-right tile position + var rowItr = this.pixelToTileCoords( + rect.pos.x - tileset.tilewidth, + rect.pos.y - tileset.tileheight, + me.pool.pull("me.Vector2d") + ).floorSelf(); + var TileEnd = this.pixelToTileCoords( + rect.pos.x + rect.width + tileset.tilewidth, + rect.pos.y + rect.height + tileset.tileheight, + me.pool.pull("me.Vector2d") + ).ceilSelf(); + + var rectEnd = this.tileToPixelCoords(TileEnd.x, TileEnd.y, me.pool.pull("me.Vector2d")); + + // Determine the tile and pixel coordinates to start at + var startPos = this.tileToPixelCoords(rowItr.x, rowItr.y, me.pool.pull("me.Vector2d")); + startPos.x -= this.hTilewidth; + startPos.y += this.tileheight; + + /* Determine in which half of the tile the top-left corner of the area we + * need to draw is. If we're in the upper half, we need to start one row + * up due to those tiles being visible as well. How we go up one row + * depends on whether we're in the left or right half of the tile. + */ + var inUpperHalf = startPos.y - rect.pos.y > this.hTileheight; + var inLeftHalf = rect.pos.x - startPos.x < this.hTilewidth; + + if (inUpperHalf) { + if (inLeftHalf) { + rowItr.x--; + startPos.x -= this.hTilewidth; + } + else { + rowItr.y--; + startPos.x += this.hTilewidth; + } + startPos.y -= this.hTileheight; + } + + // Determine whether the current row is shifted half a tile to the right + var shifted = inUpperHalf ^ inLeftHalf; + + // initialize the columItr vector + var columnItr = rowItr.clone(); + + // main drawing loop + for (var y = startPos.y * 2; y - this.tileheight * 2 < rectEnd.y * 2; y += this.tileheight) { + columnItr.setV(rowItr); + for (var x = startPos.x; x < rectEnd.x; x += this.tilewidth) { + //check if it's valid tile, if so render + if ( + (columnItr.x >= 0) && + (columnItr.y >= 0) && + (columnItr.x < this.cols) && + (columnItr.y < this.rows) + ) { + var tmxTile = layer.layerData[columnItr.x][columnItr.y]; + if (tmxTile) { + tileset = tmxTile.tileset; + // offset could be different per tileset + offset = tileset.tileoffset; + // draw our tile + tileset.drawTile( + renderer, + offset.x + x, + offset.y + y / 2 - tileset.tileheight, + tmxTile + ); + } + } + // Advance to the next column + columnItr.x++; + columnItr.y--; + } + + // Advance to the next row + if (!shifted) { + rowItr.x++; + startPos.x += this.hTilewidth; + shifted = true; + } + else { + rowItr.y++; + startPos.x -= this.hTilewidth; + shifted = false; + } + } + + me.pool.push(rowItr); + me.pool.push(TileEnd); + me.pool.push(rectEnd); + me.pool.push(startPos); + } + }); + + + /** + * an Hexagonal Map Renderder + * @memberOf me + * @extends me.TMXRenderer + * @memberOf me + * @constructor + * @param {Number} cols width of the tilemap in tiles + * @param {Number} rows height of the tilemap in tiles + * @param {Number} tilewidth width of each tile in pixels + * @param {Number} tileheight height of each tile in pixels + */ + me.TMXHexagonalRenderer = me.TMXRenderer.extend({ + // constructor + init: function (cols, rows, tilewidth, tileheight, hexsidelength, staggeraxis, staggerindex) { + me.TMXRenderer.prototype.init.apply(this, [ + cols, + rows, + tilewidth, + tileheight + ]); + + this.hexsidelength = hexsidelength; + this.staggeraxis = staggeraxis; + this.staggerindex = staggerindex; + + this.sidelengthx = 0; + this.sidelengthy = 0; + + if (staggeraxis === "x") { + this.sidelengthx = hexsidelength; + } + else { + this.sidelengthy = hexsidelength; + } + + this.sideoffsetx = (this.tilewidth - this.sidelengthx) / 2; + this.sideoffsety = (this.tileheight - this.sidelengthy) / 2; + + this.columnwidth = this.sideoffsetx + this.sidelengthx; + this.rowheight = this.sideoffsety + this.sidelengthy; + + this.centers = [ + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d(), + new me.Vector2d() + ]; + }, + + /** + * return true if the renderer can render the specified layer + * @ignore + */ + canRender : function (layer) { + return ( + (layer.orientation === "hexagonal") && + me.TMXRenderer.prototype.canRender.apply(this, [ layer ]) + ); + }, + + /** + * return the tile position corresponding to the specified pixel + * @ignore + */ + pixelToTileCoords : function (x, y, v) { + var q, r; + var ret = v || new me.Vector2d(); + + if (this.staggeraxis === "x") { //flat top + x = x - ((this.staggerindex === "odd") ? this.sideoffsetx : this.tilewidth); + } + else { //pointy top + y = y - ((this.staggerindex === "odd") ? this.sideoffsety : this.tileheight); + } + + // Start with the coordinates of a grid-aligned tile + var referencePoint = me.pool.pull("me.Vector2d", + Math.floor(x / (this.columnwidth * 2)), + Math.floor((y / (this.rowheight * 2))) + ); + + // Relative x and y position on the base square of the grid-aligned tile + var rel = me.pool.pull("me.Vector2d", + x - referencePoint.x * (this.columnwidth * 2), + y - referencePoint.y * (this.rowheight * 2) + ); + + // Adjust the reference point to the correct tile coordinates + if (this.staggeraxis === "x") { + referencePoint.x = referencePoint.x * 2; + if (this.staggerindex === "even") { + ++referencePoint.x; + } + } + else { + referencePoint.y = referencePoint.y * 2; + if (this.staggerindex === "even") { + ++referencePoint.y; + } + } + + // Determine the nearest hexagon tile by the distance to the center + var left, top, centerX, centerY; + if (this.staggeraxis === "x") { + left = this.sidelengthx / 2; + centerX = left + this.columnwidth; + centerY = this.tileheight / 2; + + this.centers[0].set(left, centerY); + this.centers[1].set(centerX, centerY - this.rowheight); + this.centers[2].set(centerX, centerY + this.rowheight); + this.centers[3].set(centerX + this.columnwidth, centerY); + } + else { + top = this.sidelengthy / 2; + centerX = this.tilewidth / 2; + centerY = top + this.rowheight; + + this.centers[0].set(centerX, top); + this.centers[1].set(centerX - this.columnwidth, centerY); + this.centers[2].set(centerX + this.columnwidth, centerY); + this.centers[3].set(centerX, centerY + this.rowheight); + } + + var nearest = 0; + var minDist = Number.MAX_VALUE; + var dc; + for (var i = 0; i < 4; ++i) { + dc = Math.pow(this.centers[i].x - rel.x, 2) + Math.pow(this.centers[i].y - rel.y, 2); + if (dc < minDist) { + minDist = dc; + nearest = i; + } + } + + var offsets = (this.staggeraxis === "x") ? offsetsStaggerX : offsetsStaggerY; + + q = referencePoint.x + offsets[nearest].x; + r = referencePoint.y + offsets[nearest].y; + + me.pool.push(referencePoint); + me.pool.push(rel); + + return ret.set(q, r); + }, + + /** + * return the tile position corresponding for the given X coordinate + * @ignore + */ + pixelToTileX : function (x, y) { + var ret = me.pool.pull("me.Vector2d"); + this.pixelToTileCoords(x, y, ret); + me.pool.push(ret); + return ret.x; + }, + + /** + * return the tile position corresponding for the given Y coordinates + * @ignore + */ + pixelToTileY : function (y, x) { + var ret = me.pool.pull("me.Vector2d"); + this.pixelToTileCoords(x, y, ret); + me.pool.push(ret); + return ret.y; + }, + + /** + * return the pixel position corresponding of the specified tile + * @ignore + */ + tileToPixelCoords : function (q, r, v) { + var x, y; + var ret = v || new me.Vector2d(); + if (this.staggeraxis === "x") { + //flat top + x = q * this.columnwidth; + if (this.staggerindex === "odd") { + y = r * (this.tileheight + this.sidelengthy); + y = y + (this.rowheight * (q & 1)); + } + else { + y = r * (this.tileheight + this.sidelengthy); + y = y + (this.rowheight * (1 - (q & 1))); + } + } + else { + //pointy top + y = r * this.rowheight; + if (this.staggerindex === "odd") { + x = q * (this.tilewidth + this.sidelengthx); + x = x + (this.columnwidth * (r & 1)); + } + else { + x = q * (this.tilewidth + this.sidelengthx); + x = x + (this.columnwidth * (1 - (r & 1))); + } + } + return ret.set(x, y); + }, + + /** + * fix the position of Objects to match + * the way Tiled places them + * @ignore + */ + adjustPosition: function (obj) { + // only adjust position if obj.gid is defined + if (typeof(obj.gid) === "number") { + // Tiled objects origin point is "bottom-left" in Tiled, + // "top-left" in melonJS) + obj.y -= obj.height; + } + }, + + /** + * draw the tile map + * @ignore + */ + drawTile : function (renderer, x, y, tmxTile) { + var tileset = tmxTile.tileset; + var point = this.tileToPixelCoords(x, y, me.pool.pull("me.Vector2d")); + + // draw the tile + tileset.drawTile( + renderer, + tileset.tileoffset.x + point.x, + tileset.tileoffset.y + point.y + (this.tileheight - tileset.tileheight), + tmxTile + ); + + me.pool.push(point); + }, + + /** + * draw the tile map + * @ignore + */ + drawTileLayer : function (renderer, layer, rect) { + // get top-left and bottom-right tile position + var start = this.pixelToTileCoords( + rect.pos.x, + rect.pos.y + ).floorSelf(); + + var end = this.pixelToTileCoords( + rect.pos.x + rect.width + this.tilewidth, + rect.pos.y + rect.height + this.tileheight + ).ceilSelf(); + + //ensure we are in the valid tile range + start.x = start.x < 0 ? 0 : start.x; + start.y = start.y < 0 ? 0 : start.y; + end.x = end.x > this.cols ? this.cols : end.x; + end.y = end.y > this.rows ? this.rows : end.y; + + // main drawing loop + for (var y = start.y; y < end.y; y++) { + for (var x = start.x; x < end.x; x++) { + var tmxTile = layer.layerData[x][y]; + if (tmxTile) { + this.drawTile(renderer, x, y, tmxTile); + } + } + } + } + }); + +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + + /** + * a generic Color Layer Object + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {String} name Layer name + * @param {me.Color|String} color CSS color + * @param {Number} z z-index position + */ + me.ColorLayer = me.Renderable.extend({ + /** + * Constructor + * @ignore + */ + init: function (name, color, z) { + // parent constructor + me.Renderable.prototype.init.apply(this, [0, 0, Infinity, Infinity]); + + // apply given parameters + this.name = name; + this.pos.z = z; + this.floating = true; + + // parse the given color + if (color instanceof me.Color) { + this.color = color.toRGBA(); + } else { + // string (#RGB, #ARGB, #RRGGBB, #AARRGGBB) + var _col = me.pool.pull("me.Color"); + this.color = _col.parseCSS(color).toRGBA(); + me.pool.push(_col); + } + + + }, + + /** + * draw the color layer + * @ignore + */ + draw : function (renderer, rect) { + // set layer opacity + var _alpha = renderer.globalAlpha(); + renderer.setGlobalAlpha(_alpha * this.getOpacity()); + + var vpos = me.game.viewport.pos; + renderer.setColor(this.color); + renderer.fillRect( + rect.left - vpos.x, rect.top - vpos.y, + rect.width, rect.height + ); + // restore context alpha value + renderer.setGlobalAlpha(_alpha); + renderer.setColor("#fff"); + } + }); + + /** + * a generic Image Layer Object + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @param {Object} settings ImageLayer properties + * @param {Image|String} settings.image Image reference. See {@link me.loader.getImage} + * @param {String} [settings.name="me.ImageLayer"] Layer name + * @param {Number} [settings.z=0] z-index position + * @param {Number|me.Vector2d} [settings.ratio=1.0] Scrolling ratio to be applied + * @param {Number|me.Vector2d} [settings.anchorPoint=0.0] Image origin. See {@link me.ImageLayer#anchorPoint} + */ + me.ImageLayer = me.Renderable.extend({ + /** + * constructor + * @ignore + */ + init: function (x, y, settings) { + // layer name + this.name = settings.name || "me.ImageLayer"; + + // get the corresponding image + this.image = me.utils.getImage(settings.image); + + // XXX: Keep this check? + if (!this.image) { + throw new me.Error(( + (typeof(settings.image) === "string") ? + "'" + settings.image + "'" : + "Image" + ) + " file for Image Layer '" + this.name + "' not found!"); + } + + this.imagewidth = this.image.width; + this.imageheight = this.image.height; + + // call the constructor + me.Renderable.prototype.init.apply(this, [x, y, Infinity, Infinity]); + + // render in screen coordinates + this.floating = true; + + // displaying order + this.pos.z = settings.z || 0; + + this.offset = new me.Vector2d(x, y); + + /** + * Define the image scrolling ratio
+ * Scrolling speed is defined by multiplying the viewport delta position (e.g. followed entity) by the specified ratio. + * Setting this vector to <0.0,0.0> will disable automatic scrolling.
+ * To specify a value through Tiled, use one of the following format :
+ * - a number, to change the value for both axis
+ * - a json expression like `json:{"x":0.5,"y":0.5}` if you wish to specify a different value for both x and y + * @public + * @type me.Vector2d + * @default <1.0,1.0> + * @name me.ImageLayer#ratio + */ + this.ratio = new me.Vector2d(1.0, 1.0); + + if (typeof(settings.ratio) !== "undefined") { + // little hack for backward compatiblity + if (typeof(settings.ratio) === "number") { + this.ratio.set(settings.ratio, settings.ratio); + } else /* vector */ { + this.ratio.setV(settings.ratio); + } + } + + if (typeof(settings.anchorPoint) === "undefined") { + /** + * Define how the image is anchored to the viewport bounds
+ * By default, its upper-left corner is anchored to the viewport bounds upper left corner.
+ * The anchorPoint is a unit vector where each component falls in range [0.0,1.0].
+ * Some common examples:
+ * * <0.0,0.0> : (Default) Anchor image to the upper-left corner of viewport bounds + * * <0.5,0.5> : Center the image within viewport bounds + * * <1.0,1.0> : Anchor image to the lower-right corner of viewport bounds + * To specify a value through Tiled, use one of the following format :
+ * - a number, to change the value for both axis
+ * - a json expression like `json:{"x":0.5,"y":0.5}` if you wish to specify a different value for both x and y + * @public + * @type me.Vector2d + * @default <0.0,0.0> + * @name me.ImageLayer#anchorPoint + */ + this.anchorPoint.set(0, 0); + } + else { + if (typeof(settings.anchorPoint) === "number") { + this.anchorPoint.set(settings.anchorPoint, settings.anchorPoint); + } + else /* vector */ { + this.anchorPoint.setV(settings.anchorPoint); + } + } + + /** + * Define if and how an Image Layer should be repeated.
+ * By default, an Image Layer is repeated both vertically and horizontally.
+ * Acceptable values :
+ * * 'repeat' - The background image will be repeated both vertically and horizontally. (default)
+ * * 'repeat-x' - The background image will be repeated only horizontally.
+ * * 'repeat-y' - The background image will be repeated only vertically.
+ * * 'no-repeat' - The background-image will not be repeated.
+ * @public + * @type String + * @name me.ImageLayer#repeat + */ + Object.defineProperty(this, "repeat", { + /** + * @ignore + */ + get : function get() { + return this._repeat; + }, + /** + * @ignore + */ + set : function set(val) { + this._repeat = val; + switch (this._repeat) { + case "no-repeat" : + this.repeatX = false; + this.repeatY = false; + break; + case "repeat-x" : + this.repeatX = true; + this.repeatY = false; + break; + case "repeat-y" : + this.repeatX = false; + this.repeatY = true; + break; + default : // "repeat" + this.repeatX = true; + this.repeatY = true; + break; + } + this.resize(me.game.viewport.width, me.game.viewport.height); + this.createPattern(); + } + }); + + this.repeat = settings.repeat || "repeat"; + }, + + // called when the layer is added to the game world or a container + onActivateEvent : function () { + var _updateLayerFn = this.updateLayer.bind(this); + // register to the viewport change notification + this.vpChangeHdlr = me.event.subscribe(me.event.VIEWPORT_ONCHANGE, _updateLayerFn); + this.vpResizeHdlr = me.event.subscribe(me.event.VIEWPORT_ONRESIZE, this.resize.bind(this)); + this.vpLoadedHdlr = me.event.subscribe(me.event.LEVEL_LOADED, function() { + // force a first refresh when the level is loaded + _updateLayerFn(me.game.viewport.pos); + }); + // in case the level is not added to the root container, + // the onActivateEvent call happens after the LEVEL_LOADED event + // so we need to force a first update + if (this.ancestor._root !== true) { + this.updateLayer(me.game.viewport.pos); + } + }, + + /** + * resize the Image Layer to match the given size + * @name resize + * @memberOf me.ImageLayer + * @function + * @param {Number} w new width + * @param {Number} h new height + */ + resize : function (w, h) { + me.Renderable.prototype.resize.apply(this, [ + this.repeatX ? Infinity : w, + this.repeatY ? Infinity : h + ]); + }, + + /** + * createPattern function + * @ignore + * @function + */ + createPattern : function () { + this._pattern = me.video.renderer.createPattern(this.image, this._repeat); + }, + + /** + * updateLayer function + * @ignore + * @function + */ + updateLayer : function (vpos) { + var rx = this.ratio.x, + ry = this.ratio.y; + + if (rx === ry === 0) { + // static image + return; + } + + var viewport = me.game.viewport, + width = this.imagewidth, + height = this.imageheight, + bw = viewport.bounds.width, + bh = viewport.bounds.height, + ax = this.anchorPoint.x, + ay = this.anchorPoint.y, + + /* + * Automatic positioning + * + * See https://github.com/melonjs/melonJS/issues/741#issuecomment-138431532 + * for a thorough description of how this works. + */ + x = ax * (rx - 1) * (bw - viewport.width) + this.offset.x - rx * vpos.x, + y = ay * (ry - 1) * (bh - viewport.height) + this.offset.y - ry * vpos.y; + + + // Repeat horizontally; start drawing from left boundary + if (this.repeatX) { + this.pos.x = x % width; + } + else { + this.pos.x = x; + } + + // Repeat vertically; start drawing from top boundary + if (this.repeatY) { + this.pos.y = y % height; + } + else { + this.pos.y = y; + } + }, + + /** + * draw the image layer + * @ignore + */ + draw : function (renderer) { + var viewport = me.game.viewport, + width = this.imagewidth, + height = this.imageheight, + bw = viewport.bounds.width, + bh = viewport.bounds.height, + ax = this.anchorPoint.x, + ay = this.anchorPoint.y, + x = this.pos.x, + y = this.pos.y, + alpha = renderer.globalAlpha(); + + if (this.ratio.x === this.ratio.y === 0) { + x = x + ax * (bw - width); + y = y + ay * (bh - height); + } + + renderer.setGlobalAlpha(alpha * this.getOpacity()); + renderer.translate(x, y); + renderer.drawPattern( + this._pattern, + 0, + 0, + viewport.width * 2, + viewport.height * 2 + ); + renderer.translate(-x, -y); + renderer.setGlobalAlpha(alpha); + }, + + // called when the layer is removed from the game world or a container + onDeactivateEvent : function () { + // cancel all event subscriptions + me.event.unsubscribe(this.vpChangeHdlr); + me.event.unsubscribe(this.vpResizeHdlr); + me.event.unsubscribe(this.vpLoadedHdlr); + } + + }); + + /** + * a TMX Tile Layer Object + * Tiled QT 0.7.x format + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {Number} tilewidth width of each tile in pixels + * @param {Number} tileheight height of each tile in pixels + * @param {String} orientation "isometric" or "orthogonal" + * @param {me.TMXTilesetGroup} tilesets tileset as defined in Tiled + * @param {Number} z z-index position + */ + me.TMXLayer = me.Renderable.extend({ + /** + * @ignore + */ + init: function (tilewidth, tileheight, orientation, tilesets, z) { + // super constructor + me.Renderable.prototype.init.apply(this, [0, 0, 0, 0]); + + // tile width & height + this.tilewidth = tilewidth; + this.tileheight = tileheight; + + // layer orientation + this.orientation = orientation; + + /** + * The Layer corresponding Tilesets + * @public + * @type me.TMXTilesetGroup + * @name me.TMXLayer#tilesets + */ + this.tilesets = tilesets; + + // the default tileset + // XXX: Is this even used? + this.tileset = (this.tilesets ? this.tilesets.getTilesetByIndex(0) : null); + + // Biggest tile size to draw + this.maxTileSize = { + "width" : 0, + "height" : 0 + }; + for (var i = 0; i < this.tilesets.length; i++) { + var tileset = this.tilesets.getTilesetByIndex(i); + this.maxTileSize.width = Math.max(this.maxTileSize.width, tileset.tilewidth); + this.maxTileSize.height = Math.max(this.maxTileSize.height, tileset.tileheight); + } + + /** + * All animated tilesets in this layer + * @ignore + * @type Array + * @name me.TMXLayer#animatedTilesets + */ + this.animatedTilesets = []; + + /** + * Layer contains tileset animations + * @public + * @type Boolean + * @name me.TMXLayer#isAnimated + */ + this.isAnimated = false; + + // for displaying order + this.pos.z = z; + + // tiled default coordinates are top-left + this.anchorPoint.set(0, 0); + }, + + /** @ignore */ + initFromJSON: function (layer) { + // additional TMX flags + this.name = layer.name; + this.cols = +layer.width; + this.rows = +layer.height; + + // hexagonal maps only + this.hexsidelength = +layer.hexsidelength || undefined; + this.staggeraxis = layer.staggeraxis; + this.staggerindex = layer.staggerindex; + + // layer opacity + var visible = typeof(layer.visible) !== "undefined" ? +layer.visible : 1; + this.setOpacity(visible ? +layer.opacity : 0); + + // layer "real" size + if (this.orientation === "isometric") { + this.width = (this.cols + this.rows) * (this.tilewidth / 2); + this.height = (this.cols + this.rows) * (this.tileheight / 2); + } else { + this.width = this.cols * this.tilewidth; + this.height = this.rows * this.tileheight; + } + // check if we have any user-defined properties + me.TMXUtils.applyTMXProperties(this, layer); + + // check for the correct rendering method + if (typeof (this.preRender) === "undefined") { + this.preRender = me.sys.preRender; + } + + // if pre-rendering method is use, create an offline canvas/renderer + if (this.preRender === true) { + this.canvasRenderer = new me.CanvasRenderer( + me.video.createCanvas(this.width, this.height), + this.width, this.height, + { transparent : true } + ); + } + + //initialize the layer data array + this.initArray(this.cols, this.rows); + }, + + // called when the layer is added to the game world or a container + onActivateEvent : function () { + + // (re)initialize the layer data array + /*if (this.layerData === undefined) { + this.initArray(this.cols, this.rows); + }*/ + + if (this.animatedTilesets === undefined) { + this.animatedTilesets = []; + } + + if (this.tilesets) { + var tileset = this.tilesets.tilesets; + for (var i = 0; i < tileset.length; i++) { + if (tileset[i].isAnimated) { + this.animatedTilesets.push(tileset[i]); + } + } + } + + this.isAnimated = this.animatedTilesets.length > 0; + + // Force pre-render off when tileset animation is used + if (this.isAnimated) { + this.preRender = false; + } + + // Resize the bounding rect + this.getBounds().resize(this.width, this.height); + }, + + // called when the layer is removed from the game world or a container + onDeactivateEvent : function () { + // clear all allocated objects + //this.layerData = undefined; + this.animatedTilesets = undefined; + }, + + /** + * Se the TMX renderer for this layer object + * @name setRenderer + * @memberOf me.TMXLayer + * @public + * @function + * @param {me.TMXRenderer} renderer + */ + setRenderer : function (renderer) { + this.renderer = renderer; + }, + + /** + * Return the layer current renderer object + * @name getRenderer + * @memberOf me.TMXLayer + * @public + * @function + * @return {me.TMXRenderer} renderer + */ + getRenderer : function (renderer) { + return this.renderer; + }, + + /** + * Create all required arrays + * @ignore + */ + initArray : function (w, h) { + // initialize the array + this.layerData = []; + for (var x = 0; x < w; x++) { + this.layerData[x] = []; + for (var y = 0; y < h; y++) { + this.layerData[x][y] = null; + } + } + }, + + /** + * Return the TileId of the Tile at the specified position + * @name getTileId + * @memberOf me.TMXLayer + * @public + * @function + * @param {Number} x X coordinate (in world/pixels coordinates) + * @param {Number} y Y coordinate (in world/pixels coordinates) + * @return {Number} TileId or null if there is no Tile at the given position + */ + getTileId : function (x, y) { + var tile = this.getTile(x, y); + return (tile ? tile.tileId : null); + }, + + /** + * Return the Tile object at the specified position + * @name getTile + * @memberOf me.TMXLayer + * @public + * @function + * @param {Number} x X coordinate (in world/pixels coordinates) + * @param {Number} y Y coordinate (in world/pixels coordinates) + * @return {me.Tile} corresponding tile or null if outside of the map area + * @example + * // get the TMX Map Layer called "Front layer" + * var layer = me.game.world.getChildByName("Front Layer")[0]; + * // get the tile object corresponding to the latest pointer position + * var tile = layer.getTile(me.input.pointer.pos.x, me.input.pointer.pos.y); + */ + getTile : function (x, y) { + if (this.containsPoint(x, y)) { + var renderer = this.renderer; + var col = ~~renderer.pixelToTileX(x, y); + var row = ~~renderer.pixelToTileY(y, x); + if ((col >= 0 && col < renderer.cols) && (row >= 0 && row < renderer.rows)) { + return this.layerData[col][row]; + } + } + // return null if no corresponding tile + return null; + }, + + /** + * Create a new Tile at the specified position + * @name setTile + * @memberOf me.TMXLayer + * @public + * @function + * @param {Number} x X coordinate (in map coordinates: row/column) + * @param {Number} y Y coordinate (in map coordinates: row/column) + * @param {Number} tileId tileId + * @return {me.Tile} the corresponding newly created tile object + */ + setTile : function (x, y, tileId) { + if (!this.tileset.contains(tileId)) { + // look for the corresponding tileset + this.tileset = this.tilesets.getTilesetByGid(tileId); + } + var tile = this.layerData[x][y] = new me.Tile(x, y, tileId, this.tileset); + // draw the corresponding tile + if (this.preRender) { + this.renderer.drawTile(this.canvasRenderer, x, y, tile); + } + return tile; + }, + + /** + * clear the tile at the specified position + * @name clearTile + * @memberOf me.TMXLayer + * @public + * @function + * @param {Number} x X coordinate (in map coordinates: row/column) + * @param {Number} y Y coordinate (in map coordinates: row/column) + * @example + * me.game.world.getChildByType(me.TMXLayer).forEach(function(layer) { + * // clear all tiles at the given x,y coordinates + * layer.clearTile(x, y); + * }); + */ + clearTile : function (x, y) { + // clearing tile + this.layerData[x][y] = null; + // erase the corresponding area in the canvas + if (this.preRender) { + this.canvasRenderer.clearRect(x * this.tilewidth, y * this.tileheight, this.tilewidth, this.tileheight); + } + }, + + /** + * update animations in a tileset layer + * @ignore + */ + update : function (dt) { + if (this.isAnimated) { + var result = false; + for (var i = 0; i < this.animatedTilesets.length; i++) { + result = this.animatedTilesets[i].update(dt) || result; + } + return result; + } + + return false; + }, + + /** + * draw a tileset layer + * @ignore + */ + draw : function (renderer, rect) { + // set the layer alpha value + var alpha = renderer.globalAlpha(); + renderer.setGlobalAlpha(alpha * this.getOpacity()); + + // use the offscreen canvas + if (this.preRender) { + var width = Math.min(rect.width, this.width); + var height = Math.min(rect.height, this.height); + + // draw using the cached canvas + renderer.drawImage( + this.canvasRenderer.getCanvas(), + rect.pos.x, rect.pos.y, // sx,sy + width, height, // sw,sh + rect.pos.x, rect.pos.y, // dx,dy + width, height // dw,dh + ); + } + // dynamically render the layer + else { + // draw the layer + this.renderer.drawTileLayer(renderer, this, rect); + } + + // restore context to initial state + renderer.setGlobalAlpha(alpha); + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * Tile QT +0.7.x format + * http://www.mapeditor.org/ + * + */ +(function () { + + // constant to identify the collision object layer + var COLLISION_GROUP = "collision"; + + /** + * set a compatible renderer object + * for the specified map + * @ignore + */ + function getNewDefaultRenderer(obj) { + switch (obj.orientation) { + case "orthogonal": + return new me.TMXOrthogonalRenderer( + obj.cols, + obj.rows, + obj.tilewidth, + obj.tileheight + ); + + case "isometric": + return new me.TMXIsometricRenderer( + obj.cols, + obj.rows, + obj.tilewidth, + obj.tileheight + ); + + case "hexagonal": + return new me.TMXHexagonalRenderer( + obj.cols, + obj.rows, + obj.tilewidth, + obj.tileheight, + obj.hexsidelength, + obj.staggeraxis, + obj.staggerindex + ); + + // if none found, throw an exception + default: + throw new me.Error(obj.orientation + " type TMX Tile Map not supported!"); + } + } + + /** + * Set tiled layer Data + * @ignore + */ + function setLayerData(layer, data) { + var idx = 0; + // set everything + for (var y = 0; y < layer.rows; y++) { + for (var x = 0; x < layer.cols; x++) { + // get the value of the gid + var gid = data[idx++]; + // fill the array + if (gid !== 0) { + // add a new tile to the layer + layer.setTile(x, y, gid); + } + } + } + } + + /** + * read the layer Data + * @ignore + */ + function readLayer(map, data, z) { + var layer = new me.TMXLayer(map.tilewidth, map.tileheight, map.orientation, map.tilesets, z); + // init the layer properly + layer.initFromJSON(data); + // set a renderer + if (!map.getRenderer().canRender(layer)) { + layer.setRenderer(getNewDefaultRenderer(layer)); + } + else { + // use the default one + layer.setRenderer(map.getRenderer()); + } + // parse the layer data + setLayerData(layer, + me.TMXUtils.decode( + data.data, + data.encoding, + data.compression + ) + ); + return layer; + } + + /** + * read the Image Layer Data + * @ignore + */ + function readImageLayer(map, data, z) { + // Normalize properties + me.TMXUtils.applyTMXProperties(data.properties, data); + + // create the layer + var imageLayer = new me.ImageLayer( + +data.x || 0, + +data.y || 0, + Object.assign({ + name: data.name, + image: data.image, + z: z + }, data.properties) + ); + + // set some additional flags + var visible = typeof(data.visible) !== "undefined" ? data.visible : true; + imageLayer.setOpacity(visible ? +data.opacity : 0); + + return imageLayer; + } + + /** + * read the tileset Data + * @ignore + */ + function readTileset(data) { + return (new me.TMXTileset(data)); + } + + /** + * read the object group Data + * @ignore + */ + function readObjectGroup(map, data, z) { + return (new me.TMXObjectGroup(map, data, z)); + } + + + /** + * a TMX Tile Map Object + * Tiled QT +0.7.x format + * @class + * @extends me.Object + * @memberOf me + * @constructor + * @param {String} levelId name of TMX map + * @param {Object} data TMX map in JSON format + * @example + * // create a new level object based on the TMX JSON object + * var level = new me.TMXTileMap(levelId, me.loader.getTMX(levelId)); + * // add the level to the game world container + * level.addTo(me.game.world, true); + */ + me.TMXTileMap = me.Object.extend({ + // constructor + init: function (levelId, data) { + + /** + * name of the tilemap + * @public + * @type String + * @name me.TMXTileMap#name + */ + this.name = levelId; + + /** + * the level data (JSON) + * @ignore + */ + this.data = data; + + /** + * width of the tilemap in tiles + * @public + * @type Int + * @name me.TMXTileMap#cols + */ + this.cols = +data.width; + /** + * height of the tilemap in tiles + * @public + * @type Int + * @name me.TMXTileMap#rows + */ + this.rows = +data.height; + + /** + * Tile width + * @public + * @type Int + * @name me.TMXTileMap#tilewidth + */ + this.tilewidth = +data.tilewidth; + + /** + * Tile height + * @public + * @type Int + * @name me.TMXTileMap#tileheight + */ + this.tileheight = +data.tileheight; + + // tilesets for this map + this.tilesets = null; + // layers + this.layers = []; + // group objects + this.objectGroups = []; + + // tilemap version + this.version = data.version; + + // map type (orthogonal or isometric) + this.orientation = data.orientation; + if (this.orientation === "isometric") { + this.width = (this.cols + this.rows) * (this.tilewidth / 2); + this.height = (this.cols + this.rows) * (this.tileheight / 2); + } else { + this.width = this.cols * this.tilewidth; + this.height = this.rows * this.tileheight; + } + + + // objects minimum z order + this.z = 0; + + // object id + this.nextobjectid = +data.nextobjectid || undefined; + + + // hex/iso properties + this.hexsidelength = +data.hexsidelength || undefined; + this.staggeraxis = data.staggeraxis; + this.staggerindex = data.staggerindex; + + // background color + this.backgroundcolor = data.backgroundcolor; + + // set additional map properties (if any) + me.TMXUtils.applyTMXProperties(this, data); + + // internal flag + this.initialized = false; + + }, + + /** + * Return the map default renderer + * @name getRenderer + * @memberOf me.TMXTileMap + * @public + * @function + * @return {me.TMXRenderer} renderer + */ + getRenderer : function (renderer) { + if ((typeof(this.renderer) === "undefined") || (!this.renderer.canRender(this))) { + this.renderer = getNewDefaultRenderer(this); + } + return this.renderer; + }, + + /** + * parse the map + * @ignore + */ + readMapObjects: function (data) { + + if (this.initialized === true) { + return; + } + + // to automatically increment z index + var zOrder = this.z; + var self = this; + + // Tileset information + if (!this.tilesets) { + // make sure we have a TilesetGroup Object + this.tilesets = new me.TMXTilesetGroup(); + } + + // parse all tileset objects + if (typeof (data.tilesets) !== "undefined") { + var tilesets = data.tilesets; + tilesets.forEach(function (tileset) { + // add the new tileset + self.tilesets.add(readTileset(tileset)); + }); + } + + // check if a user-defined background color is defined + if (this.backgroundcolor) { + this.layers.push( + new me.ColorLayer( + "background_color", + this.backgroundcolor, + zOrder++ + ) + ); + } + + // check if a background image is defined + if (this.background_image) { + // add a new image layer + this.layers.push(new me.ImageLayer( + 0, 0, { + name : "background_image", + image : this.background_image, + z : zOrder++ + } + )); + } + + data.layers.forEach(function (layer) { + switch (layer.type) { + case "imagelayer": + self.layers.push(readImageLayer(self, layer, zOrder++)); + break; + + case "tilelayer": + self.layers.push(readLayer(self, layer, zOrder++)); + break; + + // get the object groups information + case "objectgroup": + self.objectGroups.push(readObjectGroup(self, layer, zOrder++)); + break; + + default: + break; + } + }); + this.initialized = true; + }, + + + /** + * add all the map layers and objects to the given container + * @name me.TMXTileMap#addTo + * @public + * @function + * @param {me.Container} target container + * @param {boolean} flatten if true, flatten all objects into the given container + * @example + * // create a new level object based on the TMX JSON object + * var level = new me.TMXTileMap(levelId, me.loader.getTMX(levelId)); + * // add the level to the game world container + * level.addTo(me.game.world, true); + */ + addTo : function (container, flatten) { + var _sort = container.autoSort; + var _depth = container.autoDepth; + + // disable auto-sort and auto-depth + container.autoSort = false; + container.autoDepth = false; + + // add all layers instances + this.getLayers().forEach(function (layer) { + container.addChild(layer); + }); + + // add all Object instances + this.getObjects(flatten).forEach(function (object) { + container.addChild(object); + }); + + // set back auto-sort and auto-depth + container.autoSort = _sort; + container.autoDepth = _depth; + + // force a sort + container.sort(true); + }, + + /** + * return an Array of instantiated objects, based on the map object definition + * @name me.TMXTileMap#getObjects + * @public + * @function + * @param {boolean} flatten if true, flatten all objects into the returned array,
+ * ignoring all defined groups (no sub containers will be created) + * @return {me.Renderable[]} Array of Objects + */ + getObjects : function (flatten) { + var objects = []; + var isCollisionGroup = false; + var targetContainer; + + // parse the map for objects + this.readMapObjects(this.data); + + for (var g = 0; g < this.objectGroups.length; g++) { + var group = this.objectGroups[g]; + + // check if this is the collision shape group + isCollisionGroup = group.name.toLowerCase().includes(COLLISION_GROUP); + + if (flatten === false) { + // create a new container + targetContainer = new me.Container(0, 0, this.width, this.height); + + // set additional properties + targetContainer.name = group.name; + targetContainer.pos.z = group.z; + targetContainer.setOpacity(group.opacity); + + // disable auto-sort and auto-depth + targetContainer.autoSort = false; + targetContainer.autoDepth = false; + } + + // iterate through the group and add all object into their + // corresponding target Container + for (var o = 0; o < group.objects.length; o++) { + // TMX object settings + var settings = group.objects[o]; + + var obj = me.pool.pull( + settings.name || "me.Entity", + settings.x, settings.y, + settings + ); + // skip if the pull function does not return a corresponding object + if (typeof obj !== "object") { + continue; + } + + // check if a me.Tile object is embedded + if (typeof (settings.tile) === "object" && !obj.renderable) { + obj.renderable = settings.tile.getRenderable(settings); + // adjust position if necessary + switch (settings.rotation) { + case Math.PI: + obj.translate(-obj.renderable.width, obj.renderable.height); + break; + case Math.PI / 2 : + obj.translate(0, obj.renderable.height); + break; + case -(Math.PI / 2) : + obj.translate(-obj.renderable.width, 0); + break; + default : + // this should not happen + break; + } + } + + if (isCollisionGroup && !settings.name) { + // configure the body accordingly + obj.body.collisionType = me.collision.types.WORLD_SHAPE; + } + + // set the obj z order correspondingly to its parent container/group + obj.pos.z = group.z; + + //apply group opacity value to the child objects if group are merged + if (flatten === true) { + if (obj.isRenderable === true) { + obj.setOpacity(obj.getOpacity() * group.opacity); + // and to child renderables if any + if (obj.renderable instanceof me.Renderable) { + obj.renderable.setOpacity(obj.renderable.getOpacity() * group.opacity); + } + } + // directly add the obj into the objects array + objects.push(obj); + } else /* false*/ { + // add it to the new container + targetContainer.addChild(obj); + } + + } + + // if we created a new container + if ((flatten === false) && (targetContainer.children.length > 0)) { + + // re-enable auto-sort and auto-depth + targetContainer.autoSort = true; + targetContainer.autoDepth = true; + + // add our container to the world + objects.push(targetContainer); + } + } + return objects; + }, + + /** + * return all the existing layers + * @name me.TMXTileMap#getLayers + * @public + * @function + * @return {me.TMXLayer[]} Array of Layers + */ + getLayers : function () { + // parse the map for objects + this.readMapObjects(this.data); + return this.layers; + }, + + /** + * destroy function, clean all allocated objects + * @name me.TMXTileMap#destroy + * @public + * @function + */ + destroy : function () { + this.tilesets = undefined; + this.layers = []; + this.objectGroups = []; + this.initialized = false; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * a level manager object
+ * once ressources loaded, the level director contains all references of defined levels
+ * There is no constructor function for me.levelDirector, this is a static object + * @namespace me.levelDirector + * @memberOf me + */ + me.levelDirector = (function () { + // hold public stuff in our singletong + var api = {}; + + /* + * PRIVATE STUFF + */ + + // our levels + var levels = {}; + // level index table + var levelIdx = []; + // current level index + var currentLevelIdx = 0; + // onresize handler + var onresize_handler = null; + + function safeLoadLevel(levelId, options, restart) { + // clean the destination container + options.container.destroy(); + + // reset the renderer + me.video.renderer.reset(); + + // clean the current (previous) level + if (levels[api.getCurrentLevelId()]) { + levels[api.getCurrentLevelId()].destroy(); + } + + // update current level index + currentLevelIdx = levelIdx.indexOf(levelId); + + // add the specified level to the game world + loadTMXLevel(levelId, options.container, options.flatten, options.setViewportBounds); + + // publish the corresponding message + me.event.publish(me.event.LEVEL_LOADED, [ levelId ]); + + // fire the callback + options.onLoaded(levelId); + + if (restart) { + // resume the game loop if it was previously running + me.state.restart(); + } + } + + /** + * Load a TMX level + * @name loadTMXLevel + * @memberOf me.game + * @private + * @param {String} level level id + * @param {me.Container} target container + * @param {boolean} flatten if true, flatten all objects into the given container + * @param {boolean} setViewportBounds if true, set the viewport bounds to the map size + * @ignore + * @function + */ + function loadTMXLevel(levelId, container, flatten, setViewportBounds) { + var level = levels[levelId]; + + // disable auto-sort for the given container + var autoSort = container.autoSort; + container.autoSort = false; + + if (setViewportBounds) { + // update the viewport bounds + me.game.viewport.setBounds( + 0, 0, + Math.max(level.width, me.game.viewport.width), + Math.max(level.height, me.game.viewport.height) + ); + } + + // reset the GUID generator + // and pass the level id as parameter + me.utils.resetGUID(levelId, level.nextobjectid); + + // add all level elements to the target container + level.addTo(container, flatten); + + // sort everything (recursively) + container.sort(true); + container.autoSort = autoSort; + + container.resize(level.width, level.height); + + function resize_container() { + // center the map if smaller than the current viewport + container.pos.set( + Math.max(0, ~~((me.game.viewport.width - level.width) / 2)), + Math.max(0, ~~((me.game.viewport.height - level.height) / 2)), + 0 + ); + } + + if (setViewportBounds) { + resize_container(); + + // Replace the resize handler + if (onresize_handler) { + me.event.unsubscribe(onresize_handler); + } + onresize_handler = me.event.subscribe(me.event.VIEWPORT_ONRESIZE, resize_container); + } + } + + /* + * PUBLIC STUFF + */ + + /** + * reset the level director + * @ignore + */ + api.reset = function () {}; + + /** + * add a level + * @ignore + */ + api.addLevel = function () { + throw new me.Error("no level loader defined"); + }; + + /** + * add a TMX level + * @ignore + */ + api.addTMXLevel = function (levelId, callback) { + // just load the level with the XML stuff + if (levels[levelId] == null) { + //console.log("loading "+ levelId); + levels[levelId] = new me.TMXTileMap(levelId, me.loader.getTMX(levelId)); + // level index + levelIdx.push(levelId); + } + else { + //console.log("level %s already loaded", levelId); + return false; + } + + // call the callback if defined + if (callback) { + callback(); + } + // true if level loaded + return true; + }; + + /** + * load a level into the game manager
+ * (will also create all level defined entities, etc..) + * @name loadLevel + * @memberOf me.levelDirector + * @public + * @function + * @param {String} level level id + * @param {Object} [options] additional optional parameters + * @param {me.Container} [options.container=me.game.world] container in which to load the specified level + * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded + * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container + * @param {boolean} [options.setViewportBounds=true] if true, set the viewport bounds to the map size + * @example + * // the game assets to be be preloaded + * // TMX maps + * var resources = [ + * {name: "a4_level1", type: "tmx", src: "data/level/a4_level1.tmx"}, + * {name: "a4_level2", type: "tmx", src: "data/level/a4_level2.tmx"}, + * {name: "a4_level3", type: "tmx", src: "data/level/a4_level3.tmx"}, + * // ... + * ]; + * + * // ... + * + * // load a level into the game world + * me.levelDirector.loadLevel("a4_level1"); + * ... + * ... + * // load a level into a specific container + * var levelContainer = new me.Container(); + * me.levelDirector.loadLevel("a4_level2", {container:levelContainer}); + * // add a simple transformation + * levelContainer.currentTransform.translate(levelContainer.width / 2, levelContainer.height / 2 ); + * levelContainer.currentTransform.rotate(0.05); + * levelContainer.currentTransform.translate(-levelContainer.width / 2, -levelContainer.height / 2 ); + * // add it to the game world + * me.game.world.addChild(levelContainer); + */ + api.loadLevel = function (levelId, options) { + options = Object.assign({ + "container" : me.game.world, + "onLoaded" : me.game.onLevelLoaded, + "flatten" : me.game.mergeGroup, + "setViewportBounds" : true + }, options || {}); + + // throw an exception if not existing + if (typeof(levels[levelId]) === "undefined") { + throw new me.Error("level " + levelId + " not found"); + } + + if (levels[levelId] instanceof me.TMXTileMap) { + + // check the status of the state mngr + var wasRunning = me.state.isRunning(); + + if (wasRunning) { + // stop the game loop to avoid + // some silly side effects + me.state.stop(); + + safeLoadLevel.defer(this, levelId, options, true); + } + else { + safeLoadLevel(levelId, options); + } + } + else { + throw new me.Error("no level loader defined"); + } + return true; + }; + + /** + * return the current level id
+ * @name getCurrentLevelId + * @memberOf me.levelDirector + * @public + * @function + * @return {String} + */ + api.getCurrentLevelId = function () { + return levelIdx[currentLevelIdx]; + }; + + /** + * return the current level definition. + * for a reference to the live instantiated level, + * rather use the container in which it was loaded (e.g. me.game.world) + * @name getCurrentLevel + * @memberOf me.levelDirector + * @public + * @function + * @return {me.TMXTileMap} + */ + api.getCurrentLevel = function () { + return levels[api.getCurrentLevelId()]; + }; + + /** + * reload the current level
+ * @name reloadLevel + * @memberOf me.levelDirector + * @public + * @function + * @param {Object} [options] additional optional parameters + * @param {me.Container} [options.container=me.game.world] container in which to load the specified level + * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded + * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container + */ + api.reloadLevel = function (options) { + // reset the level to initial state + //levels[currentLevel].reset(); + return api.loadLevel(api.getCurrentLevelId(), options); + }; + + /** + * load the next level
+ * @name nextLevel + * @memberOf me.levelDirector + * @public + * @function + * @param {Object} [options] additional optional parameters + * @param {me.Container} [options.container=me.game.world] container in which to load the specified level + * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded + * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container + */ + api.nextLevel = function (options) { + //go to the next level + if (currentLevelIdx + 1 < levelIdx.length) { + return api.loadLevel(levelIdx[currentLevelIdx + 1], options); + } + else { + return false; + } + }; + + /** + * load the previous level
+ * @name previousLevel + * @memberOf me.levelDirector + * @public + * @function + * @param {Object} [options] additional optional parameters + * @param {me.Container} [options.container=me.game.world] container in which to load the specified level + * @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded + * @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container + */ + api.previousLevel = function (options) { + // go to previous level + if (currentLevelIdx - 1 >= 0) { + return api.loadLevel(levelIdx[currentLevelIdx - 1], options); + } + else { + return false; + } + }; + + /** + * return the amount of level preloaded
+ * @name levelCount + * @memberOf me.levelDirector + * @public + * @function + */ + api.levelCount = function () { + return levelIdx.length; + }; + + // return our object + return api; + })(); +})(); + +/** + * @preserve Tween JS + * https://github.com/sole/Tween.js + */ + +/* eslint-disable quotes, keyword-spacing, comma-spacing, no-return-assign */ + +(function() { + + /** + * Javascript Tweening Engine

+ * Super simple, fast and easy to use tweening engine which incorporates optimised Robert Penner's equation

+ * https://github.com/sole/Tween.js

+ * author sole / http://soledadpenades.com
+ * author mr.doob / http://mrdoob.com
+ * author Robert Eisele / http://www.xarg.org
+ * author Philippe / http://philippe.elsass.me
+ * author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html
+ * author Paul Lewis / http://www.aerotwist.com/
+ * author lechecacharro
+ * author Josh Faul / http://jocafa.com/ + * @class + * @memberOf me + * @constructor + * @param {Object} object object on which to apply the tween + * @example + * // add a tween to change the object pos.y variable to 200 in 3 seconds + * tween = new me.Tween(myObject.pos).to({y: 200}, 3000).onComplete(myFunc); + * tween.easing(me.Tween.Easing.Bounce.Out); + * tween.start(); + */ + me.Tween = function ( object ) { + + var _object = null; + var _valuesStart = null; + var _valuesEnd = null; + var _valuesStartRepeat = null; + var _duration = null; + var _repeat = null; + var _yoyo = null; + var _reversed = null; + var _delayTime = null; + var _startTime = null; + var _easingFunction = null; + var _interpolationFunction = null; + var _chainedTweens = null; + var _onStartCallback = null; + var _onStartCallbackFired = null; + var _onUpdateCallback = null; + var _onCompleteCallback = null; + var _tweenTimeTracker = null; + + /** + * @ignore + */ + this._resumeCallback = function (elapsed) { + if (_startTime) { + _startTime += elapsed; + } + }; + + /** + * @ignore + */ + this.setProperties = function (object) { + _object = object; + _valuesStart = {}; + _valuesEnd = {}; + _valuesStartRepeat = {}; + _duration = 1000; + _repeat = 0; + _yoyo = false; + _reversed = false; + _delayTime = 0; + _startTime = null; + _easingFunction = me.Tween.Easing.Linear.None; + _interpolationFunction = me.Tween.Interpolation.Linear; + _chainedTweens = []; + _onStartCallback = null; + _onStartCallbackFired = false; + _onUpdateCallback = null; + _onCompleteCallback = null; + _tweenTimeTracker = me.timer.lastUpdate; + + // reset the persistent flag to default value + this.isPersistent = false; + + // Set all starting values present on the target object + for ( var field in object ) { + if(typeof object !== 'object') { + _valuesStart[ field ] = parseFloat(object[field], 10); + } + } + }; + + this.setProperties(object); + + /** + * reset the tween object to default value + * @ignore + */ + this.onResetEvent = function ( object ) { + this.setProperties(object); + }; + + /** + * subscribe to the resume event when added + * @ignore + */ + this.onActivateEvent = function () { + me.event.subscribe(me.event.STATE_RESUME, this._resumeCallback); + }; + + /** + * Unsubscribe when tween is removed + * @ignore + */ + this.onDeactivateEvent = function () { + me.event.unsubscribe(me.event.STATE_RESUME, this._resumeCallback); + }; + + /** + * object properties to be updated and duration + * @name me.Tween#to + * @public + * @function + * @param {Object} properties hash of properties + * @param {Number} [duration=1000] tween duration + */ + this.to = function ( properties, duration ) { + + if ( duration !== undefined ) { + + _duration = duration; + + } + + _valuesEnd = properties; + + return this; + + }; + + /** + * start the tween + * @name me.Tween#start + * @public + * @function + */ + this.start = function ( _time ) { + + _onStartCallbackFired = false; + + // add the tween to the object pool on start + me.game.world.addChild(this); + + _startTime = (typeof(_time) === 'undefined' ? me.timer.getTime() : _time) + _delayTime; + + for ( var property in _valuesEnd ) { + + // check if an Array was provided as property value + if ( _valuesEnd[ property ] instanceof Array ) { + + if ( _valuesEnd[ property ].length === 0 ) { + + continue; + + } + + // create a local copy of the Array with the start value at the front + _valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] ); + + } + + _valuesStart[ property ] = _object[ property ]; + + if( ( _valuesStart[ property ] instanceof Array ) === false ) { + _valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings + } + + _valuesStartRepeat[ property ] = _valuesStart[ property ] || 0; + + } + + return this; + + }; + + /** + * stop the tween + * @name me.Tween#stop + * @public + * @function + */ + this.stop = function () { + // remove the tween from the world container + me.game.world.removeChildNow(this); + return this; + }; + + /** + * delay the tween + * @name me.Tween#delay + * @public + * @function + * @param {Number} amount delay amount expressed in milliseconds + */ + this.delay = function ( amount ) { + + _delayTime = amount; + return this; + + }; + + /** + * Repeat the tween + * @name me.Tween#repeat + * @public + * @function + * @param {Number} times amount of times the tween should be repeated + */ + this.repeat = function ( times ) { + + _repeat = times; + return this; + + }; + + /** + * allows the tween to bounce back to their original value when finished + * @name me.Tween#yoyo + * @public + * @function + * @param {Boolean} yoyo + */ + this.yoyo = function( yoyo ) { + + _yoyo = yoyo; + return this; + + }; + + /** + * set the easing function + * @name me.Tween#easing + * @public + * @function + * @param {me.Tween.Easing} fn easing function + */ + this.easing = function ( easing ) { + if (typeof easing !== 'function') { + throw new me.Tween.Error("invalid easing function for me.Tween.easing()"); + } + _easingFunction = easing; + return this; + + }; + + /** + * set the interpolation function + * @name me.Tween#interpolation + * @public + * @function + * @param {me.Tween.Interpolation} fn interpolation function + */ + this.interpolation = function ( interpolation ) { + + _interpolationFunction = interpolation; + return this; + + }; + + /** + * chain the tween + * @name me.Tween#chain + * @public + * @function + * @param {me.Tween} chainedTween Tween to be chained + */ + this.chain = function () { + + _chainedTweens = arguments; + return this; + + }; + + /** + * onStart callback + * @name me.Tween#onStart + * @public + * @function + * @param {Function} onStartCallback callback + */ + this.onStart = function ( callback ) { + + _onStartCallback = callback; + return this; + + }; + + /** + * onUpdate callback + * @name me.Tween#onUpdate + * @public + * @function + * @param {Function} onUpdateCallback callback + */ + this.onUpdate = function ( callback ) { + + _onUpdateCallback = callback; + return this; + + }; + + /** + * onComplete callback + * @name me.Tween#onComplete + * @public + * @function + * @param {Function} onCompleteCallback callback + */ + this.onComplete = function ( callback ) { + + _onCompleteCallback = callback; + return this; + + }; + + /** @ignore */ + this.update = function ( dt ) { + + // the original Tween implementation expect + // a timestamp and not a time delta + _tweenTimeTracker = (me.timer.lastUpdate > _tweenTimeTracker) ? me.timer.lastUpdate : _tweenTimeTracker + dt; + var time = _tweenTimeTracker; + + var property; + + if ( time < _startTime ) { + + return true; + + } + + if ( _onStartCallbackFired === false ) { + + if ( _onStartCallback !== null ) { + + _onStartCallback.call( _object ); + + } + + _onStartCallbackFired = true; + + } + + var elapsed = ( time - _startTime ) / _duration; + elapsed = elapsed > 1 ? 1 : elapsed; + + var value = _easingFunction( elapsed ); + + for ( property in _valuesEnd ) { + + var start = _valuesStart[ property ] || 0; + var end = _valuesEnd[ property ]; + + if ( end instanceof Array ) { + + _object[ property ] = _interpolationFunction( end, value ); + + } else { + + // Parses relative end values with start as base (e.g.: +10, -3) + if ( typeof(end) === "string" ) { + end = start + parseFloat(end, 10); + } + + // protect against non numeric properties. + if ( typeof(end) === "number" ) { + _object[ property ] = start + ( end - start ) * value; + } + + } + + } + + if ( _onUpdateCallback !== null ) { + + _onUpdateCallback.call( _object, value ); + + } + + if ( elapsed === 1 ) { + + if ( _repeat > 0 ) { + + if( isFinite( _repeat ) ) { + _repeat--; + } + + // reassign starting values, restart by making startTime = now + for( property in _valuesStartRepeat ) { + + if ( typeof( _valuesEnd[ property ] ) === "string" ) { + _valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10); + } + + if (_yoyo) { + var tmp = _valuesStartRepeat[ property ]; + _valuesStartRepeat[ property ] = _valuesEnd[ property ]; + _valuesEnd[ property ] = tmp; + } + _valuesStart[ property ] = _valuesStartRepeat[ property ]; + + } + + if (_yoyo) { + _reversed = !_reversed; + } + + _startTime = time + _delayTime; + + return true; + + } else { + // remove the tween from the world container + me.game.world.removeChildNow(this); + + if ( _onCompleteCallback !== null ) { + + _onCompleteCallback.call( _object ); + + } + + for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i ++ ) { + + _chainedTweens[ i ].start( time ); + + } + + return false; + + } + + } + + return true; + + }; + + }; + + /** + * Easing Function :
+ *

+ * me.Tween.Easing.Linear.None
+ * me.Tween.Easing.Quadratic.In
+ * me.Tween.Easing.Quadratic.Out
+ * me.Tween.Easing.Quadratic.InOut
+ * me.Tween.Easing.Cubic.In
+ * me.Tween.Easing.Cubic.Out
+ * me.Tween.Easing.Cubic.InOut
+ * me.Tween.Easing.Quartic.In
+ * me.Tween.Easing.Quartic.Out
+ * me.Tween.Easing.Quartic.InOut
+ * me.Tween.Easing.Quintic.In
+ * me.Tween.Easing.Quintic.Out
+ * me.Tween.Easing.Quintic.InOut
+ * me.Tween.Easing.Sinusoidal.In
+ * me.Tween.Easing.Sinusoidal.Out
+ * me.Tween.Easing.Sinusoidal.InOut
+ * me.Tween.Easing.Exponential.In
+ * me.Tween.Easing.Exponential.Out
+ * me.Tween.Easing.Exponential.InOut
+ * me.Tween.Easing.Circular.In
+ * me.Tween.Easing.Circular.Out
+ * me.Tween.Easing.Circular.InOut
+ * me.Tween.Easing.Elastic.In
+ * me.Tween.Easing.Elastic.Out
+ * me.Tween.Easing.Elastic.InOut
+ * me.Tween.Easing.Back.In
+ * me.Tween.Easing.Back.Out
+ * me.Tween.Easing.Back.InOut
+ * me.Tween.Easing.Bounce.In
+ * me.Tween.Easing.Bounce.Out
+ * me.Tween.Easing.Bounce.InOut + *

+ * @public + * @constant + * @type enum + * @name Easing + * @memberOf me.Tween + */ + me.Tween.Easing = { + + Linear: { + /** @ignore */ + None: function ( k ) { + + return k; + + } + + }, + + Quadratic: { + /** @ignore */ + In: function ( k ) { + + return k * k; + + }, + /** @ignore */ + Out: function ( k ) { + + return k * ( 2 - k ); + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; + return - 0.5 * ( --k * ( k - 2 ) - 1 ); + + } + + }, + + Cubic: { + /** @ignore */ + In: function ( k ) { + + return k * k * k; + + }, + /** @ignore */ + Out: function ( k ) { + + return --k * k * k + 1; + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k; + return 0.5 * ( ( k -= 2 ) * k * k + 2 ); + + } + + }, + + Quartic: { + /** @ignore */ + In: function ( k ) { + + return k * k * k * k; + + }, + /** @ignore */ + Out: function ( k ) { + + return 1 - ( --k * k * k * k ); + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k; + return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 ); + + } + + }, + + Quintic: { + /** @ignore */ + In: function ( k ) { + + return k * k * k * k * k; + + }, + /** @ignore */ + Out: function ( k ) { + + return --k * k * k * k * k + 1; + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k; + return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 ); + + } + + }, + + Sinusoidal: { + /** @ignore */ + In: function ( k ) { + + return 1 - Math.cos( k * Math.PI / 2 ); + + }, + /** @ignore */ + Out: function ( k ) { + + return Math.sin( k * Math.PI / 2 ); + + }, + /** @ignore */ + InOut: function ( k ) { + + return 0.5 * ( 1 - Math.cos( Math.PI * k ) ); + + } + + }, + + Exponential: { + /** @ignore */ + In: function ( k ) { + + return k === 0 ? 0 : Math.pow( 1024, k - 1 ); + + }, + /** @ignore */ + Out: function ( k ) { + + return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k ); + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( k === 0 ) return 0; + if ( k === 1 ) return 1; + if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 ); + return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 ); + + } + + }, + + Circular: { + /** @ignore */ + In: function ( k ) { + + return 1 - Math.sqrt( 1 - k * k ); + + }, + /** @ignore */ + Out: function ( k ) { + + return Math.sqrt( 1 - ( --k * k ) ); + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1); + return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1); + + } + + }, + + Elastic: { + /** @ignore */ + In: function ( k ) { + + var s, a = 0.1, p = 0.4; + if ( k === 0 ) return 0; + if ( k === 1 ) return 1; + if ( !a || a < 1 ) { a = 1; s = p / 4; } + else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); + return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); + + }, + /** @ignore */ + Out: function ( k ) { + + var s, a = 0.1, p = 0.4; + if ( k === 0 ) return 0; + if ( k === 1 ) return 1; + if ( !a || a < 1 ) { a = 1; s = p / 4; } + else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); + return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 ); + + }, + /** @ignore */ + InOut: function ( k ) { + + var s, a = 0.1, p = 0.4; + if ( k === 0 ) return 0; + if ( k === 1 ) return 1; + if ( !a || a < 1 ) { a = 1; s = p / 4; } + else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); + if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); + return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1; + + } + + }, + + Back: { + /** @ignore */ + In: function ( k ) { + + var s = 1.70158; + return k * k * ( ( s + 1 ) * k - s ); + + }, + /** @ignore */ + Out: function ( k ) { + + var s = 1.70158; + return --k * k * ( ( s + 1 ) * k + s ) + 1; + + }, + /** @ignore */ + InOut: function ( k ) { + + var s = 1.70158 * 1.525; + if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) ); + return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 ); + + } + + }, + + Bounce: { + /** @ignore */ + In: function ( k ) { + + return 1 - me.Tween.Easing.Bounce.Out( 1 - k ); + + }, + /** @ignore */ + Out: function ( k ) { + + if ( k < ( 1 / 2.75 ) ) { + + return 7.5625 * k * k; + + } else if ( k < ( 2 / 2.75 ) ) { + + return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; + + } else if ( k < ( 2.5 / 2.75 ) ) { + + return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; + + } else { + + return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; + + } + + }, + /** @ignore */ + InOut: function ( k ) { + + if ( k < 0.5 ) return me.Tween.Easing.Bounce.In( k * 2 ) * 0.5; + return me.Tween.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5; + + } + + } + + }; + + /** + * Interpolation Function :
+ *

+ * me.Tween.Interpolation.Linear
+ * me.Tween.Interpolation.Bezier
+ * me.Tween.Interpolation.CatmullRom + *

+ * @public + * @constant + * @type enum + * @name Interpolation + * @memberOf me.Tween + */ + me.Tween.Interpolation = { + /** @ignore */ + Linear: function ( v, k ) { + + var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = me.Tween.Interpolation.Utils.Linear; + + if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f ); + if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f ); + + return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i ); + + }, + /** @ignore */ + Bezier: function ( v, k ) { + + var b = 0, n = v.length - 1, pw = Math.pow, bn = me.Tween.Interpolation.Utils.Bernstein, i; + + for ( i = 0; i <= n; i++ ) { + b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i ); + } + + return b; + + }, + /** @ignore */ + CatmullRom: function ( v, k ) { + + var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = me.Tween.Interpolation.Utils.CatmullRom; + + if ( v[ 0 ] === v[ m ] ) { + + if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) ); + + return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i ); + + } else { + + if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] ); + if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] ); + + return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i ); + + } + + }, + + Utils: { + /** @ignore */ + Linear: function ( p0, p1, t ) { + + return ( p1 - p0 ) * t + p0; + + }, + /** @ignore */ + Bernstein: function ( n , i ) { + + var fc = me.Tween.Interpolation.Utils.Factorial; + return fc( n ) / fc( i ) / fc( n - i ); + + }, + /** @ignore */ + Factorial: ( function () { + + var a = [ 1 ]; + + return function ( n ) { + + var s = 1, i; + if ( a[ n ] ) return a[ n ]; + for ( i = n; i > 1; i-- ) s *= i; + return a[ n ] = s; + + }; + + } )(), + /** @ignore */ + CatmullRom: function ( p0, p1, p2, p3, t ) { + + var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + } + + } + + }; + + /** + * Base class for Tween exception handling. + * @name Error + * @class + * @memberOf me.Tween + * @constructor + * @param {String} msg Error message. + */ + me.Tween.Error = me.Error.extend({ + /** + * @ignore + */ + init : function (msg) { + me.Error.prototype.init.apply(this, [ msg ]); + this.name = "me.Tween.Error"; + } + }); +})(); +/* eslint-enable quotes, keyword-spacing, comma-spacing, no-return-assign */ + +/** + * @preserve MinPubSub + * a micro publish/subscribe messaging framework + * @see https://github.com/daniellmb/MinPubSub + * @author Daniel Lamb + * + * Released under the MIT License + */ +(function () { + /** + * There is no constructor function for me.event + * @namespace me.event + * @memberOf me + */ + me.event = (function () { + // hold public stuff inside the singleton + var api = {}; + + /** + * the channel/subscription hash + * @ignore + */ + var cache = {}; + + /* + * PUBLIC + */ + + /** + * Channel Constant when the game is paused
+ * Data passed : none
+ * @public + * @constant + * @type String + * @name me.event#STATE_PAUSE + */ + api.STATE_PAUSE = "me.state.onPause"; + + /** + * Channel Constant for when the game is resumed
+ * Data passed : {Number} time in ms the game was paused + * @public + * @constant + * @type String + * @name me.event#STATE_RESUME + */ + api.STATE_RESUME = "me.state.onResume"; + + /** + * Channel Constant when the game is stopped
+ * Data passed : none
+ * @public + * @constant + * @type String + * @name me.event#STATE_STOP + */ + api.STATE_STOP = "me.state.onStop"; + + /** + * Channel Constant for when the game is restarted
+ * Data passed : {Number} time in ms the game was stopped + * @public + * @constant + * @type String + * @name me.event#STATE_RESTART + */ + api.STATE_RESTART = "me.state.onRestart"; + + /** + * Channel Constant for when the game manager is initialized
+ * Data passed : none
+ * @public + * @constant + * @type String + * @name me.event#GAME_INIT + */ + api.GAME_INIT = "me.game.onInit"; + + /** + * Channel Constant for when the game manager is resetted
+ * Data passed : none
+ * @public + * @constant + * @type String + * @name me.event#GAME_RESET + */ + api.GAME_RESET = "me.game.onReset"; + + /** + * Channel Constant for when a level is loaded
+ * Data passed : {String} Level Name + * @public + * @constant + * @type String + * @name me.event#LEVEL_LOADED + */ + api.LEVEL_LOADED = "me.game.onLevelLoaded"; + + /** + * Channel Constant for when everything has loaded
+ * Data passed : none
+ * @public + * @constant + * @type String + * @name me.event#LOADER_COMPLETE + */ + api.LOADER_COMPLETE = "me.loader.onload"; + + /** + * Channel Constant for displaying a load progress indicator
+ * Data passed : {Number} [0 .. 1], {Resource} resource object
+ * @public + * @constant + * @type String + * @name me.event#LOADER_PROGRESS + */ + api.LOADER_PROGRESS = "me.loader.onProgress"; + + /** + * Channel Constant for pressing a binded key
+ * Data passed : {String} user-defined action, {Number} keyCode, + * {Boolean} edge state
+ * Edge-state is for detecting "locked" key bindings. When a locked key + * is pressed and held, the first event will have the third argument + * set true. Subsequent events will continue firing with the third + * argument set false. + * @public + * @constant + * @type String + * @name me.event#KEYDOWN + * @example + * me.input.bindKey(me.input.KEY.X, "jump", true); // Edge-triggered + * me.input.bindKey(me.input.KEY.Z, "shoot"); // Level-triggered + * me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) { + * // Checking bound keys + * if (action === "jump") { + * if (edge) { + * this.doJump(); + * } + * + * // Make character fall slower when holding the jump key + * this.vel.y = this.body.gravity; + * } + * }); + */ + api.KEYDOWN = "me.input.keydown"; + + /** + * Channel Constant for releasing a binded key
+ * Data passed : {String} user-defined action, {Number} keyCode
+ * @public + * @constant + * @type String + * @name me.event#KEYUP + * @example + * me.event.subscribe(me.event.KEYUP, function (action, keyCode) { + * // Checking unbound keys + * if (keyCode == me.input.KEY.ESC) { + * if (me.state.isPaused()) { + * me.state.resume(); + * } + * else { + * me.state.pause(); + * } + * } + * }); + */ + api.KEYUP = "me.input.keyup"; + + /** + * Channel Constant for when a gamepad is connected
+ * Data passed : {Object} gamepad object + * @public + * @constant + * @type String + * @name me.event#GAMEPAD_CONNECTED + */ + api.GAMEPAD_CONNECTED = "gamepad.connected"; + + /** + * Channel Constant for when a gamepad is disconnected
+ * Data passed : {Object} gamepad object + * @public + * @constant + * @type String + * @name me.event#GAMEPAD_DISCONNECTED + */ + api.GAMEPAD_DISCONNECTED = "gamepad.disconnected"; + + /** + * Channel Constant for when gamepad button/axis state is updated
+ * Data passed : {Number} index
+ * Data passed : {String} type : "axes" or "buttons"
+ * Data passed : {Number} button
+ * Data passed : {Number} current.value
+ * Data passed : {Boolean} current.pressed
+ * @public + * @constant + * @type String + * @name me.event#GAMEPAD_UPDATE + */ + api.GAMEPAD_UPDATE = "gamepad.update"; + + /** + * Channel Constant for pointermove events on the viewport area
+ * Data passed : {Object} the Event object
+ * @public + * @constant + * @type String + * @name me.event#POINTERMOVE + */ + api.POINTERMOVE = "me.event.pointermove"; + + /** + * Channel Constant for dragstart events on a Draggable entity
+ * Data passed: + * {Object} the drag event
+ * {Object} the Draggable entity
+ * @public + * @constant + * @type String + * @name me.event#DRAGSTART + */ + api.DRAGSTART = "me.game.dragstart"; + + /** + * Channel Constant for dragend events on a Draggable entity
+ * Data passed: + * {Object} the drag event
+ * {Object} the Draggable entity
+ * @public + * @constant + * @type String + * @name me.event#DRAGEND + */ + api.DRAGEND = "me.game.dragend"; + + /** + * Channel Constant for when the (browser) window is resized
+ * Data passed : {Event} Event object
+ * @public + * @constant + * @type String + * @name me.event#WINDOW_ONRESIZE + */ + api.WINDOW_ONRESIZE = "window.onresize"; + + /** + * Channel Constant for when the viewport is resized
+ * (this usually follows a WINDOW_ONRESIZE event, when using the `flex` scaling mode is used and after the viewport was updated).
+ * Data passed : {Number} viewport width
+ * Data passed : {Number} viewport height
+ * @public + * @constant + * @type String + * @name me.event#VIEWPORT_ONRESIZE + */ + api.VIEWPORT_ONRESIZE = "viewport.onresize"; + + /** + * Channel Constant for when the device is rotated
+ * Data passed : {Event} Event object
+ * @public + * @constant + * @type String + * @name me.event#WINDOW_ONORIENTATION_CHANGE + */ + api.WINDOW_ONORIENTATION_CHANGE = "window.orientationchange"; + + /** + * Channel Constant for when the (browser) window is scrolled
+ * Data passed : {Event} Event object
+ * @public + * @constant + * @type String + * @name me.event#WINDOW_ONSCROLL + */ + api.WINDOW_ONSCROLL = "window.onscroll"; + + /** + * Channel Constant for when the viewport position is updated
+ * Data passed : {me.Vector2d} viewport position vector
+ * @public + * @constant + * @type String + * @name me.event#VIEWPORT_ONCHANGE + */ + api.VIEWPORT_ONCHANGE = "viewport.onchange"; + + /** + * Publish some data on a channel + * @name me.event#publish + * @public + * @function + * @param {String} channel The channel to publish on + * @param {Array} arguments The data to publish + * + * @example Publish stuff on '/some/channel'. + * Anything subscribed will be called with a function + * signature like: function (a,b,c){ ... } + * + * me.event.publish("/some/channel", ["a","b","c"]); + * + */ + api.publish = function (channel, args) { + var subs = cache[channel], + len = subs ? subs.length : 0; + + //can change loop or reverse array if the order matters + while (len--) { + subs[len].apply(window, args || []); // is window correct here? + } + }; + + /** + * Register a callback on a named channel. + * @name me.event#subscribe + * @public + * @function + * @param {String} channel The channel to subscribe to + * @param {Function} callback The event handler, any time something is + * published on a subscribed channel, the callback will be called + * with the published array as ordered arguments + * @return {handle} A handle which can be used to unsubscribe this + * particular subscription + * @example + * me.event.subscribe("/some/channel", function (a, b, c){ doSomething(); }); + */ + + api.subscribe = function (channel, callback) { + if (!cache[channel]) { + cache[channel] = []; + } + cache[channel].push(callback); + return [ channel, callback ]; // Array + }; + + /** + * Disconnect a subscribed function for a channel. + * @name me.event#unsubscribe + * @public + * @function + * @param {Array|String} handle The return value from a subscribe call or the + * name of a channel as a String + * @param {Function} [callback] The callback to be unsubscribed. + * @example + * var handle = me.event.subscribe("/some/channel", function (){}); + * me.event.unsubscribe(handle); + * + * // Or alternatively ... + * + * var callback = function (){}; + * me.event.subscribe("/some/channel", callback); + * me.event.unsubscribe("/some/channel", callback); + */ + api.unsubscribe = function (handle, callback) { + var subs = cache[callback ? handle : handle[0]], + len = subs ? subs.length : 0; + + callback = callback || handle[1]; + + while (len--) { + if (subs[len] === callback) { + subs.splice(len, 1); + } + } + }; + + // return our object + return api; + })(); +})(); + +/*! + * howler.js v2.0.2 + * howlerjs.com + * + * (c) 2013-2016, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +/* eslint-disable quotes, space-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new*/ + +(function() { + + 'use strict'; + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Create the global controller. All contained methods and properties apply + * to all sounds that are currently playing or will be in the future. + */ + var HowlerGlobal = function() { + this.init(); + }; + HowlerGlobal.prototype = { + /** + * Initialize the global Howler object. + * @return {Howler} + */ + init: function() { + var self = this || Howler; + + // Internal properties. + self._codecs = {}; + self._howls = []; + self._muted = false; + self._volume = 1; + self._canPlayEvent = 'canplaythrough'; + self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null; + + // Public properties. + self.masterGain = null; + self.noAudio = false; + self.usingWebAudio = true; + self.autoSuspend = true; + self.ctx = null; + + // Set to false to disable the auto iOS enabler. + self.mobileAutoEnable = true; + + // Setup the various state values for global tracking. + self._setup(); + + return self; + }, + + /** + * Get/set the global volume for all sounds. + * @param {Float} vol Volume from 0.0 to 1.0. + * @return {Howler/Float} Returns self or current volume. + */ + volume: function(vol) { + var self = this || Howler; + vol = parseFloat(vol); + + // If we don't have an AudioContext created yet, run the setup. + if (!self.ctx) { + setupAudioContext(); + } + + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + self._volume = vol; + + // Don't update any of the nodes if we are muted. + if (self._muted) { + return self; + } + + // When using Web Audio, we just need to adjust the master gain. + if (self.usingWebAudio) { + self.masterGain.gain.value = vol; + } + + // Loop through and change volume for all HTML5 audio nodes. + for (var i=0; i=0; i--) { + self._howls[i].unload(); + } + + // Create a new AudioContext to make sure it is fully reset. + if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') { + self.ctx.close(); + self.ctx = null; + setupAudioContext(); + } + + return self; + }, + + /** + * Check for codec support of specific extension. + * @param {String} ext Audio file extention. + * @return {Boolean} + */ + codecs: function(ext) { + return (this || Howler)._codecs[ext.replace(/^x-/, '')]; + }, + + /** + * Setup various state values for global tracking. + * @return {Howler} + */ + _setup: function() { + var self = this || Howler; + + // Keeps track of the suspend/resume state of the AudioContext. + self.state = self.ctx ? self.ctx.state || 'running' : 'running'; + + // Automatically begin the 30-second suspend process + self._autoSuspend(); + + // Check if audio is available. + if (!self.usingWebAudio) { + // No audio is available on this system if noAudio is set to true. + if (typeof Audio !== 'undefined') { + try { + var test = new Audio(); + + // Check if the canplaythrough event is available. + if (typeof test.oncanplaythrough === 'undefined') { + self._canPlayEvent = 'canplay'; + } + } catch(e) { + self.noAudio = true; + } + } else { + self.noAudio = true; + } + } + + // Test to make sure audio isn't disabled in Internet Explorer. + try { + var test = new Audio(); + if (test.muted) { + self.noAudio = true; + } + } catch (e) {} + + // Check for supported codecs. + if (!self.noAudio) { + self._setupCodecs(); + } + + return self; + }, + + /** + * Check for browser support for various codecs and cache the results. + * @return {Howler} + */ + _setupCodecs: function() { + var self = this || Howler; + var audioTest = null; + + // Must wrap in a try/catch because IE11 in server mode throws an error. + try { + audioTest = (typeof Audio !== 'undefined') ? new Audio() : null; + } catch (err) { + return self; + } + + if (!audioTest || typeof audioTest.canPlayType !== 'function') { + return self; + } + + var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''); + + // Opera version <33 has mixed MP3 support, so we need to check for and block it. + var checkOpera = self._navigator && self._navigator.userAgent.match(/OPR\/([0-6].)/g); + var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33); + + self._codecs = { + mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))), + mpeg: !!mpegTest, + opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''), + ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + wav: !!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''), + aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''), + caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''), + m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + weba: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ''), + webm: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ''), + dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ''), + flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '') + }; + + return self; + }, + + /** + * Mobile browsers will only allow audio to be played after a user interaction. + * Attempt to automatically unlock audio on the first user interaction. + * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ + * @return {Howler} + */ + _enableMobileAudio: function() { + var self = this || Howler; + + // Only run this on mobile devices if audio isn't already eanbled. + var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi/i.test(self._navigator && self._navigator.userAgent); + var isTouch = !!(('ontouchend' in window) || (self._navigator && self._navigator.maxTouchPoints > 0) || (self._navigator && self._navigator.msMaxTouchPoints > 0)); + if (self._mobileEnabled || !self.ctx || (!isMobile && !isTouch)) { + return; + } + + self._mobileEnabled = false; + + // Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views. + // Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000. + // By calling Howler.unload(), we create a new AudioContext with the correct sampleRate. + if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) { + self._mobileUnloaded = true; + self.unload(); + } + + // Scratch buffer for enabling iOS to dispose of web audio buffers correctly, as per: + // http://stackoverflow.com/questions/24119684 + self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050); + + // Call this method on touch start to create and play a buffer, + // then check if the audio actually played to determine if + // audio has now been unlocked on iOS, Android, etc. + var unlock = function() { + // Create an empty buffer. + var source = self.ctx.createBufferSource(); + source.buffer = self._scratchBuffer; + source.connect(self.ctx.destination); + + // Play the empty buffer. + if (typeof source.start === 'undefined') { + source.noteOn(0); + } else { + source.start(0); + } + + // Setup a timeout to check that we are unlocked on the next event loop. + source.onended = function() { + source.disconnect(0); + + // Update the unlocked state and prevent this check from happening again. + self._mobileEnabled = true; + self.mobileAutoEnable = false; + + // Remove the touch start listener. + document.removeEventListener('touchend', unlock, true); + }; + }; + + // Setup a touch start listener to attempt an unlock in. + document.addEventListener('touchend', unlock, true); + + return self; + }, + + /** + * Automatically suspend the Web Audio AudioContext after no sound has played for 30 seconds. + * This saves processing/energy and fixes various browser-specific bugs with audio getting stuck. + * @return {Howler} + */ + _autoSuspend: function() { + var self = this; + + if (!self.autoSuspend || !self.ctx || typeof self.ctx.suspend === 'undefined' || !Howler.usingWebAudio) { + return; + } + + // Check if any sounds are playing. + for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000); + var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek); + var timeout = (duration * 1000) / Math.abs(sound._rate); + + // Update the parameters of the sound + sound._paused = false; + sound._ended = false; + sound._sprite = sprite; + sound._seek = seek; + sound._start = self._sprite[sprite][0] / 1000; + sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; + sound._loop = !!(sound._loop || self._sprite[sprite][2]); + + // Begin the actual playback. + var node = sound._node; + if (self._webAudio) { + // Fire this when the sound is ready to play to begin Web Audio playback. + var playWebAudio = function() { + self._refreshBuffer(sound); + + // Setup the playback params. + var vol = (sound._muted || self._muted) ? 0 : sound._volume; + node.gain.setValueAtTime(vol, Howler.ctx.currentTime); + sound._playStart = Howler.ctx.currentTime; + + // Play the sound using the supported method. + if (typeof node.bufferSource.start === 'undefined') { + sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration); + } else { + sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration); + } + + // Start a new timer if none is present. + if (timeout !== Infinity) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + if (!internal) { + setTimeout(function() { + self._emit('play', sound._id); + }, 0); + } + }; + + var isRunning = (Howler.state === 'running'); + if (self._state === 'loaded' && isRunning) { + playWebAudio(); + } else { + // Wait for the audio to load and then begin playback. + self.once(isRunning ? 'load' : 'resume', playWebAudio, isRunning ? sound._id : null); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } else { + // Fire this when the sound is ready to play to begin HTML5 Audio playback. + var playHtml5 = function() { + node.currentTime = seek; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.volume = sound._volume * Howler.volume(); + node.playbackRate = sound._rate; + + setTimeout(function() { + node.play(); + + // Setup the new end timer. + if (timeout !== Infinity) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + if (!internal) { + self._emit('play', sound._id); + } + }, 0); + }; + + // Play immediately if ready, or wait for the 'canplaythrough'e vent. + var loadedNoReadyState = (self._state === 'loaded' && (window && window.ejecta || !node.readyState && Howler._navigator.isCocoonJS)); + if (node.readyState === 4 || loadedNoReadyState) { + playHtml5(); + } else { + var listener = function() { + // Begin playback. + playHtml5(); + + // Clear this listener. + node.removeEventListener(Howler._canPlayEvent, listener, false); + }; + node.addEventListener(Howler._canPlayEvent, listener, false); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } + + return sound._id; + }, + + /** + * Pause playback and save current position. + * @param {Number} id The sound ID (empty to pause all in group). + * @return {Howl} + */ + pause: function(id) { + var self = this; + + // If the sound hasn't loaded, add it to the load queue to pause when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'pause', + action: function() { + self.pause(id); + } + }); + + return self; + } + + // If no id is passed, get all ID's to be paused. + var ids = self._getSoundIds(id); + + for (var i=0; i Returns the group's volume value. + * volume(id) -> Returns the sound id's current volume. + * volume(vol) -> Sets the volume of all sounds in this Howl group. + * volume(vol, id) -> Sets the volume of passed sound id. + * @return {Howl/Number} Returns self or current volume. + */ + volume: function() { + var self = this; + var args = arguments; + var vol, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the value of the groups' volume. + return self._volume; + } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') { + // First check if this is an ID, and if not, assume it is a new volume. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + vol = parseFloat(args[0]); + } + } else if (args.length >= 2) { + vol = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the volume or return the current volume. + var sound; + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + // If the sound hasn't loaded, add it to the load queue to change volume when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'volume', + action: function() { + self.volume.apply(self, args); + } + }); + + return self; + } + + // Set the group volume. + if (typeof id === 'undefined') { + self._volume = vol; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i to ? 'out' : 'in'; + var steps = diff / 0.01; + var stepLen = (steps > 0) ? len / steps : len; + + // Since browsers clamp timeouts to 4ms, we need to clamp our steps to that too. + if (stepLen < 4) { + steps = Math.ceil(steps / (4 / stepLen)); + stepLen = 4; + } + + // If the sound hasn't loaded, add it to the load queue to fade when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'fade', + action: function() { + self.fade(from, to, len, id); + } + }); + + return self; + } + + // Set the volume to the start position. + self.volume(from, id); + + // Fade the volume of one or all sounds. + var ids = self._getSoundIds(id); + for (var i=0; i 0) { + vol += (dir === 'in' ? 0.01 : -0.01); + } + + // Make sure the volume is in the right bounds. + vol = Math.max(0, vol); + vol = Math.min(1, vol); + + // Round to within 2 decimal points. + vol = Math.round(vol * 100) / 100; + + // Change the volume. + if (self._webAudio) { + if (typeof id === 'undefined') { + self._volume = vol; + } + + sound._volume = vol; + } else { + self.volume(vol, soundId, true); + } + + // When the fade is complete, stop it and fire event. + if (vol === to) { + clearInterval(sound._interval); + sound._interval = null; + self.volume(vol, soundId); + self._emit('fade', soundId); + } + }.bind(self, ids[i], sound), stepLen); + } + } + + return self; + }, + + /** + * Internal method that stops the currently playing fade when + * a new fade starts, volume is changed or the sound is stopped. + * @param {Number} id The sound id. + * @return {Howl} + */ + _stopFade: function(id) { + var self = this; + var sound = self._soundById(id); + + if (sound && sound._interval) { + if (self._webAudio) { + sound._node.gain.cancelScheduledValues(Howler.ctx.currentTime); + } + + clearInterval(sound._interval); + sound._interval = null; + self._emit('fade', id); + } + + return self; + }, + + /** + * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments. + * loop() -> Returns the group's loop value. + * loop(id) -> Returns the sound id's loop value. + * loop(loop) -> Sets the loop value for all sounds in this Howl group. + * loop(loop, id) -> Sets the loop value of passed sound id. + * @return {Howl/Boolean} Returns self or current loop value. + */ + loop: function() { + var self = this; + var args = arguments; + var loop, id, sound; + + // Determine the values for loop and id. + if (args.length === 0) { + // Return the grou's loop value. + return self._loop; + } else if (args.length === 1) { + if (typeof args[0] === 'boolean') { + loop = args[0]; + self._loop = loop; + } else { + // Return this sound's loop value. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._loop : false; + } + } else if (args.length === 2) { + loop = args[0]; + id = parseInt(args[1], 10); + } + + // If no id is passed, get all ID's to be looped. + var ids = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current playback rate. + * rate(id) -> Returns the sound id's current playback rate. + * rate(rate) -> Sets the playback rate of all sounds in this Howl group. + * rate(rate, id) -> Sets the playback rate of passed sound id. + * @return {Howl/Number} Returns self or the current playback rate. + */ + rate: function() { + var self = this; + var args = arguments; + var rate, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current rate of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new rate value. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + rate = parseFloat(args[0]); + } + } else if (args.length === 2) { + rate = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the playback rate or return the current value. + var sound; + if (typeof rate === 'number') { + // If the sound hasn't loaded, add it to the load queue to change playback rate when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'rate', + action: function() { + self.rate.apply(self, args); + } + }); + + return self; + } + + // Set the group rate. + if (typeof id === 'undefined') { + self._rate = rate; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current seek position. + * seek(id) -> Returns the sound id's current seek position. + * seek(seek) -> Sets the seek position of the first sound node. + * seek(seek, id) -> Sets the seek position of passed sound id. + * @return {Howl/Number} Returns self or the current seek position. + */ + seek: function() { + var self = this; + var args = arguments; + var seek, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current position of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new seek position. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + id = self._sounds[0]._id; + seek = parseFloat(args[0]); + } + } else if (args.length === 2) { + seek = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // If there is no ID, bail out. + if (typeof id === 'undefined') { + return self; + } + + // If the sound hasn't loaded, add it to the load queue to seek when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'seek', + action: function() { + self.seek.apply(self, args); + } + }); + + return self; + } + + // Get the sound. + var sound = self._soundById(id); + + if (sound) { + if (typeof seek === 'number' && seek >= 0) { + // Pause the sound and update position for restarting playback. + var playing = self.playing(id); + if (playing) { + self.pause(id, true); + } + + // Move the position of the track and cancel timer. + sound._seek = seek; + sound._ended = false; + self._clearTimer(id); + + // Restart the playback if the sound was playing. + if (playing) { + self.play(id, true); + } + + // Update the seek position for HTML5 Audio. + if (!self._webAudio && sound._node) { + sound._node.currentTime = seek; + } + + self._emit('seek', id); + } else { + if (self._webAudio) { + var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0; + var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0; + return sound._seek + (rateSeek + realTime * Math.abs(sound._rate)); + } else { + return sound._node.currentTime; + } + } + } + + return self; + }, + + /** + * Check if a specific sound is currently playing or not (if id is provided), or check if at least one of the sounds in the group is playing or not. + * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked. + * @return {Boolean} True if playing and false if not. + */ + playing: function(id) { + var self = this; + + // Check the passed sound ID (if any). + if (typeof id === 'number') { + var sound = self._soundById(id); + return sound ? !sound._paused : false; + } + + // Otherwise, loop through all sounds and check if any are playing. + for (var i=0; i= 0) { + Howler._howls.splice(index, 1); + } + } + + // Delete this sound from the cache (if no other Howl is using it). + var remCache = true; + for (i=0; i=0; i--) { + if (!events[i].id || events[i].id === id || event === 'load') { + setTimeout(function(fn) { + fn.call(this, id, msg); + }.bind(self, events[i].fn), 0); + + // If this event was setup with `once`, remove it. + if (events[i].once) { + self.off(event, events[i].fn, events[i].id); + } + } + } + + return self; + }, + + /** + * Queue of actions initiated before the sound has loaded. + * These will be called in sequence, with the next only firing + * after the previous has finished executing (even if async like play). + * @return {Howl} + */ + _loadQueue: function() { + var self = this; + + if (self._queue.length > 0) { + var task = self._queue[0]; + + // don't move onto the next task until this one is done + self.once(task.event, function() { + self._queue.shift(); + self._loadQueue(); + }); + + task.action(); + } + + return self; + }, + + /** + * Fired when playback ends at the end of the duration. + * @param {Sound} sound The sound object to work with. + * @return {Howl} + */ + _ended: function(sound) { + var self = this; + var sprite = sound._sprite; + + // Should this sound loop? + var loop = !!(sound._loop || self._sprite[sprite][2]); + + // Fire the ended event. + self._emit('end', sound._id); + + // Restart the playback for HTML5 Audio loop. + if (!self._webAudio && loop) { + self.stop(sound._id, true).play(sound._id); + } + + // Restart this timer if on a Web Audio loop. + if (self._webAudio && loop) { + self._emit('play', sound._id); + sound._seek = sound._start || 0; + sound._rateSeek = 0; + sound._playStart = Howler.ctx.currentTime; + + var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + // Mark the node as paused. + if (self._webAudio && !loop) { + sound._paused = true; + sound._ended = true; + sound._seek = sound._start || 0; + sound._rateSeek = 0; + self._clearTimer(sound._id); + + // Clean up the buffer source. + self._cleanBuffer(sound._node); + + // Attempt to auto-suspend AudioContext if no sounds are still playing. + Howler._autoSuspend(); + } + + // When using a sprite, end the track. + if (!self._webAudio && !loop) { + self.stop(sound._id); + } + + return self; + }, + + /** + * Clear the end timer for a sound playback. + * @param {Number} id The sound ID. + * @return {Howl} + */ + _clearTimer: function(id) { + var self = this; + + if (self._endTimers[id]) { + clearTimeout(self._endTimers[id]); + delete self._endTimers[id]; + } + + return self; + }, + + /** + * Return the sound identified by this ID, or return null. + * @param {Number} id Sound ID + * @return {Object} Sound object or null. + */ + _soundById: function(id) { + var self = this; + + // Loop through all sounds and find the one with this ID. + for (var i=0; i=0; i--) { + if (cnt <= limit) { + return; + } + + if (self._sounds[i]._ended) { + // Disconnect the audio source when using Web Audio. + if (self._webAudio && self._sounds[i]._node) { + self._sounds[i]._node.disconnect(0); + } + + // Remove sounds until we have the pool size. + self._sounds.splice(i, 1); + cnt--; + } + } + }, + + /** + * Get all ID's from the sounds pool. + * @param {Number} id Only return one ID if one is passed. + * @return {Array} Array of IDs. + */ + _getSoundIds: function(id) { + var self = this; + + if (typeof id === 'undefined') { + var ids = []; + for (var i=0; i 0) { + cache[self._src] = buffer; + loadSound(self, buffer); + } + }, function() { + self._emit('loaderror', null, 'Decoding audio data failed.'); + }); + }; + + /** + * Sound is now loaded, so finish setting everything up and fire the loaded event. + * @param {Howl} self + * @param {Object} buffer The decoded buffer sound source. + */ + var loadSound = function(self, buffer) { + // Set the duration. + if (buffer && !self._duration) { + self._duration = buffer.duration; + } + + // Setup a sprite if none is defined. + if (Object.keys(self._sprite).length === 0) { + self._sprite = {__default: [0, self._duration * 1000]}; + } + + // Fire the loaded event. + if (self._state !== 'loaded') { + self._state = 'loaded'; + self._emit('load'); + self._loadQueue(); + } + }; + + /** + * Setup the audio context when available, or switch to HTML5 Audio mode. + */ + var setupAudioContext = function() { + // Check if we are using Web Audio and setup the AudioContext if we are. + try { + if (typeof AudioContext !== 'undefined') { + Howler.ctx = new AudioContext(); + } else if (typeof webkitAudioContext !== 'undefined') { + Howler.ctx = new webkitAudioContext(); + } else { + Howler.usingWebAudio = false; + } + } catch(e) { + Howler.usingWebAudio = false; + } + + // Check if a webview is being used on iOS8 or earlier (rather than the browser). + // If it is, disable Web Audio as it causes crashing. + var iOS = (/iP(hone|od|ad)/.test(Howler._navigator && Howler._navigator.platform)); + var appVersion = Howler._navigator && Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); + var version = appVersion ? parseInt(appVersion[1], 10) : null; + if (iOS && version && version < 9) { + var safari = /safari/.test(Howler._navigator && Howler._navigator.userAgent.toLowerCase()); + if (Howler._navigator && Howler._navigator.standalone && !safari || Howler._navigator && !Howler._navigator.standalone && !safari) { + Howler.usingWebAudio = false; + } + } + + // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage). + if (Howler.usingWebAudio) { + Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); + Howler.masterGain.gain.value = 1; + Howler.masterGain.connect(Howler.ctx.destination); + } + + // Re-run the setup on Howler. + Howler._setup(); + }; + + // Add support for AMD (Asynchronous Module Definition) libraries such as require.js. + if (typeof define === 'function' && define.amd) { + define([], function() { + return { + Howler: Howler, + Howl: Howl + }; + }); + } + + // Add support for CommonJS libraries such as browserify. + if (typeof exports !== 'undefined') { + exports.Howler = Howler; + exports.Howl = Howl; + } + + // Define globally in case AMD is not available or unused. + if (typeof window !== 'undefined') { + window.HowlerGlobal = HowlerGlobal; + window.Howler = Howler; + window.Howl = Howl; + window.Sound = Sound; + } else if (typeof global !== 'undefined') { // Add to global in Node.js (for testing, etc). + global.HowlerGlobal = HowlerGlobal; + global.Howler = Howler; + global.Howl = Howl; + global.Sound = Sound; + } +})(); +/* eslint-enable quotes, space-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new */ + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ +(function () { + /** + * There is no constructor function for me.plugins
+ * This namespace is a container for all registered plugins. + * @see me.plugin.register + * @namespace me.plugins + * @memberOf me + */ + me.plugins = {}; + + /** + * There is no constructor function for me.plugin + * @namespace me.plugin + * @memberOf me + */ + me.plugin = (function () { + + // hold public stuff inside the singleton + var singleton = {}; + + /*-------------- + PUBLIC + --------------*/ + + /** + * a base Object for plugin
+ * plugin must be installed using the register function + * @see me.plugin + * @class + * @extends Object + * @name plugin.Base + * @memberOf me + * @constructor + */ + singleton.Base = me.Object.extend( + /** @scope me.plugin.Base.prototype */ + { + /** @ignore */ + init : function () { + /** + * define the minimum required version of melonJS
+ * this can be overridden by the plugin + * @public + * @type String + * @default "4.1.0" + * @name me.plugin.Base#version + */ + this.version = "4.1.0"; + } + }); + + /** + * patch a melonJS function + * @name patch + * @memberOf me.plugin + * @public + * @function + * @param {Object} proto target object + * @param {String} name target function + * @param {Function} fn replacement function + * @example + * // redefine the me.game.update function with a new one + * me.plugin.patch(me.game, "update", function () { + * // display something in the console + * console.log("duh"); + * // call the original me.game.update function + * this._patched(); + * }); + */ + singleton.patch = function (proto, name, fn) { + // use the object prototype if possible + if (typeof proto.prototype !== "undefined") { + proto = proto.prototype; + } + // reuse the logic behind me.Object.extend + if (typeof(proto[name]) === "function") { + // save the original function + var _parent = proto[name]; + // override the function with the new one + Object.defineProperty(proto, name, { + "configurable" : true, + "value" : (function (name, fn) { + return function () { + this._patched = _parent; + var ret = fn.apply(this, arguments); + this._patched = null; + return ret; + }; + })(name, fn) + }); + } + else { + console.error(name + " is not an existing function"); + } + }; + + /** + * Register a plugin. + * @name register + * @memberOf me.plugin + * @see me.plugin.Base + * @public + * @function + * @param {me.plugin.Base} plugin Plugin to instiantiate and register + * @param {String} name + * @param {} [arguments...] all extra parameters will be passed to the plugin constructor + * @example + * // register a new plugin + * me.plugin.register(TestPlugin, "testPlugin"); + * // the plugin then also become available + * // under then me.plugins namespace + * me.plugins.testPlugin.myfunction (); + */ + singleton.register = function (plugin, name) { + // ensure me.plugin[name] is not already "used" + if (me.plugin[name]) { + console.error("plugin " + name + " already registered"); + } + + // get extra arguments + var _args = []; + if (arguments.length > 2) { + // store extra arguments if any + _args = Array.prototype.slice.call(arguments, 1); + } + + // try to instantiate the plugin + _args[0] = plugin; + var instance = new (plugin.bind.apply(plugin, _args))(); + + // inheritance check + if (!instance || !(instance instanceof me.plugin.Base)) { + throw new me.Error("Plugin should extend the me.plugin.Base Class !"); + } + + // compatibility testing + if (me.sys.checkVersion(instance.version) > 0) { + throw new me.Error("Plugin version mismatch, expected: " + instance.version + ", got: " + me.version); + } + + // create a reference to the new plugin + me.plugins[name] = instance; + }; + + // return our singleton + return singleton; + })(); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * Used to make a game entity draggable + * @class + * @extends me.Entity + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the entity object + * @param {Number} y the y coordinates of the entity object + * @param {Object} settings Entity properties (see {@link me.Entity}) + */ +me.DraggableEntity = (function (Entity, Input, Event, Vector) { + "use strict"; + + return Entity.extend( + /** @scope me.DraggableEntity.prototype */ + { + /** + * Constructor + * @name init + * @memberOf me.DraggableEntity + * @function + * @param {Number} x the x postion of the entity + * @param {Number} y the y postion of the entity + * @param {Object} settings the additional entity settings + */ + init: function (x, y, settings) { + Entity.prototype.init.apply(this, [x, y, settings]); + this.dragging = false; + this.dragId = null; + this.grabOffset = new Vector(0, 0); + this.onPointerEvent = Input.registerPointerEvent; + this.removePointerEvent = Input.releasePointerEvent; + this.initEvents(); + }, + + /** + * Initializes the events the modules needs to listen to + * It translates the pointer events to me.events + * in order to make them pass through the system and to make + * this module testable. Then we subscribe this module to the + * transformed events. + * @name initEvents + * @memberOf me.DraggableEntity + * @function + */ + initEvents: function () { + var self = this; + /** + * @ignore + */ + this.mouseDown = function (e) { + this.translatePointerEvent(e, Event.DRAGSTART); + }; + /** + * @ignore + */ + this.mouseUp = function (e) { + this.translatePointerEvent(e, Event.DRAGEND); + }; + this.onPointerEvent("pointerdown", this, this.mouseDown.bind(this)); + this.onPointerEvent("pointerup", this, this.mouseUp.bind(this)); + Event.subscribe(Event.POINTERMOVE, this.dragMove.bind(this)); + Event.subscribe(Event.DRAGSTART, function (e, draggable) { + if (draggable === self) { + self.dragStart(e); + } + }); + Event.subscribe(Event.DRAGEND, function (e, draggable) { + if (draggable === self) { + self.dragEnd(e); + } + }); + }, + + /** + * Translates a pointer event to a me.event + * @name translatePointerEvent + * @memberOf me.DraggableEntity + * @function + * @param {Object} e the pointer event you want to translate + * @param {String} translation the me.event you want to translate + * the event to + */ + translatePointerEvent: function (e, translation) { + Event.publish(translation, [e, this]); + }, + + /** + * Gets called when the user starts dragging the entity + * @name dragStart + * @memberOf me.DraggableEntity + * @function + * @param {Object} x the pointer event + */ + dragStart: function (e) { + if (this.dragging === false) { + this.dragging = true; + this.dragId = e.pointerId; + this.grabOffset.set(e.gameX, e.gameY); + this.grabOffset.sub(this.pos); + return false; + } + }, + + /** + * Gets called when the user drags this entity around + * @name dragMove + * @memberOf me.DraggableEntity + * @function + * @param {Object} x the pointer event + */ + dragMove: function (e) { + if (this.dragging === true) { + if (this.dragId === e.pointerId) { + this.pos.set(e.gameX, e.gameY, this.pos.z); //TODO : z ? + this.pos.sub(this.grabOffset); + } + } + }, + + /** + * Gets called when the user stops dragging the entity + * @name dragEnd + * @memberOf me.DraggableEntity + * @function + * @param {Object} x the pointer event + */ + dragEnd: function () { + if (this.dragging === true) { + this.pointerId = undefined; + this.dragging = false; + return false; + } + }, + + /** + * Destructor + * @name destroy + * @memberOf me.DraggableEntity + * @function + */ + destroy: function () { + Event.unsubscribe(Event.POINTERMOVE, this.dragMove); + Event.unsubscribe(Event.DRAGSTART, this.dragStart); + Event.unsubscribe(Event.DRAGEND, this.dragEnd); + this.removePointerEvent("pointerdown", this); + this.removePointerEvent("pointerup", this); + } + }); +}(me.Entity, me.input, me.event, me.Vector2d)); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + */ + +/** + * Used to make a game entity a droptarget + * @class + * @extends me.Entity + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the entity object + * @param {Number} y the y coordinates of the entity object + * @param {Object} settings Entity properties (see {@link me.Entity}) + */ +me.DroptargetEntity = (function (Entity, Event) { + "use strict"; + + return Entity.extend( + /** @scope me.DroptargetEntity.prototype */ + { + /** + * Constructor + * @name init + * @memberOf me.DroptargetEntity + * @function + * @param {Number} x the x postion of the entity + * @param {Number} y the y postion of the entity + * @param {Object} settings the additional entity settings + */ + init: function (x, y, settings) { + /** + * constant for the overlaps method + * @public + * @constant + * @type String + * @name CHECKMETHOD_OVERLAP + * @memberOf me.DroptargetEntity + */ + this.CHECKMETHOD_OVERLAP = "overlaps"; + /** + * constant for the contains method + * @public + * @constant + * @type String + * @name CHECKMETHOD_CONTAINS + * @memberOf me.DroptargetEntity + */ + this.CHECKMETHOD_CONTAINS = "contains"; + /** + * the checkmethod we want to use + * @public + * @constant + * @type String + * @name checkMethod + * @memberOf me.DroptargetEntity + */ + this.checkMethod = null; + Entity.prototype.init.apply(this, [x, y, settings]); + Event.subscribe(Event.DRAGEND, this.checkOnMe.bind(this)); + this.checkMethod = this[this.CHECKMETHOD_OVERLAP]; + }, + + /** + * Sets the collision method which is going to be used to check a valid drop + * @name setCheckMethod + * @memberOf me.DroptargetEntity + * @function + * @param {Constant} checkMethod the checkmethod (defaults to CHECKMETHOD_OVERLAP) + */ + setCheckMethod: function (checkMethod) { + // We can improve this check, + // because now you can use every method in theory + if (typeof(this[checkMethod]) !== "undefined") { + this.checkMethod = this[checkMethod]; + } + }, + + /** + * Checks if a dropped entity is dropped on the current entity + * @name checkOnMe + * @memberOf me.DroptargetEntity + * @function + * @param {Object} draggableEntity the draggable entity that is dropped + */ + checkOnMe: function (e, draggableEntity) { + if (draggableEntity && this.checkMethod(draggableEntity.getBounds())) { + // call the drop method on the current entity + this.drop(draggableEntity); + } + }, + + /** + * Gets called when a draggable entity is dropped on the current entity + * @name drop + * @memberOf me.DroptargetEntity + * @function + * @param {Object} draggableEntity the draggable entity that is dropped + */ + drop: function () {}, + + /** + * Destructor + * @name destroy + * @memberOf me.DroptargetEntity + * @function + */ + destroy: function () { + Event.unsubscribe(Event.DRAGEND, this.checkOnMe); + } + }); +}(me.Entity, me.event)); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +/* + * A Collectable entity + */ + +/** + * @class + * @extends me.Entity + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the entity object + * @param {Number} y the y coordinates of the entity object + * @param {Object} settings See {@link me.Entity} + */ +me.CollectableEntity = me.Entity.extend( +/** @scope me.CollectableEntity.prototype */ +{ + /** @ignore */ + init : function (x, y, settings) { + // call the super constructor + me.Entity.prototype.init.apply(this, [x, y, settings]); + this.body.collisionType = me.collision.types.COLLECTABLE_OBJECT; + } +}); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ + +/* + * A level entity + */ + +/** + * @class + * @extends me.Entity + * @memberOf me + * @constructor + * @param {Number} x the x coordinates of the object + * @param {Number} y the y coordinates of the object + * @param {Object} settings See {@link me.Entity} + * @param {String} [settings.duration] Fade duration (in ms) + * @param {String|me.Color} [settings.color] Fade color + * @param {String} [settings.to] TMX level to load + * @param {String|me.Container} [settings.container] Target container. See {@link me.levelDirector.loadLevel} + * @param {Function} [settings.onLoaded] Level loaded callback. See {@link me.levelDirector.loadLevel} + * @param {Boolean} [settings.flatten] Flatten all objects into the target container. See {@link me.levelDirector.loadLevel} + * @param {Boolean} [settings.setViewportBounds] Resize the viewport to match the level. See {@link me.levelDirector.loadLevel} + * @example + * me.game.world.addChild(new me.LevelEntity( + * x, y, { + * "duration" : 250, + * "color" : "#000", + * "to" : "mymap2" + * } + * )); + */ +me.LevelEntity = me.Entity.extend( +/** @scope me.LevelEntity.prototype */ +{ + /** @ignore */ + init : function (x, y, settings) { + me.Entity.prototype.init.apply(this, [x, y, settings]); + + this.nextlevel = settings.to; + + this.fade = settings.fade; + this.duration = settings.duration; + this.fading = false; + + this.name = "levelEntity"; + + // a temp variable + this.gotolevel = settings.to; + + // Collect the defined level settings + this.loadLevelSettings = {}; + [ "container", "onLoaded", "flatten", "setViewportBounds" ].forEach(function (v) { + if (typeof(settings[v]) !== "undefined") { + this.loadLevelSettings[v] = settings[v]; + } + }.bind(this)); + + this.body.collisionType = me.collision.types.ACTION_OBJECT; + }, + + /** + * @ignore + */ + getlevelSettings : function () { + // Lookup for the container instance + if (typeof(this.loadLevelSettings.container) === "string") { + this.loadLevelSettings.container = me.game.world.getChildByName(this.loadLevelSettings.container)[0]; + } + return this.loadLevelSettings; + }, + + /** + * @ignore + */ + onFadeComplete : function () { + + me.levelDirector.loadLevel(this.gotolevel, this.getlevelSettings()); + me.game.viewport.fadeOut(this.fade, this.duration); + }, + + /** + * go to the specified level + * @name goTo + * @memberOf me.LevelEntity + * @function + * @param {String} [level=this.nextlevel] name of the level to load + * @protected + */ + goTo : function (level) { + this.gotolevel = level || this.nextlevel; + // load a level + //console.log("going to : ", to); + if (this.fade && this.duration) { + if (!this.fading) { + this.fading = true; + me.game.viewport.fadeIn(this.fade, this.duration, + this.onFadeComplete.bind(this)); + } + } else { + me.levelDirector.loadLevel(this.gotolevel, this.getlevelSettings()); + } + }, + + /** @ignore */ + onCollision : function () { + if (this.name === "levelEntity") { + this.goTo.apply(this); + } + return false; + } +}); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + // generate a default image for the particles + var pixel = (function () { + var canvas = me.video.createCanvas(1, 1); + var context = canvas.getContext("2d"); + context.fillStyle = "#fff"; + context.fillRect(0, 0, 1, 1); + return canvas; + })(); + + /** + * me.ParticleEmitterSettings contains the default settings for me.ParticleEmitter.
+ * + * @protected + * @class + * @memberOf me + * @see me.ParticleEmitter + */ + me.ParticleEmitterSettings = { + /** + * Width of the particle spawn area.
+ * @public + * @type Number + * @name width + * @memberOf me.ParticleEmitterSettings + * @default 0 + */ + width : 0, + + /** + * Height of the particle spawn area.
+ * @public + * @type Number + * @name height + * @memberOf me.ParticleEmitterSettings + * @default 0 + */ + height : 0, + + /** + * Image used for particles.
+ * @public + * @type CanvasImageSource + * @name image + * @memberOf me.ParticleEmitterSettings + * @default 1x1 white pixel + * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvasimagesource + */ + image : pixel, + + /** + * Total number of particles in the emitter.
+ * @public + * @type Number + * @name totalParticles + * @default 50 + * @memberOf me.ParticleEmitterSettings + */ + totalParticles : 50, + + /** + * Start angle for particle launch in Radians.
+ * @public + * @type Number + * @name angle + * @default Math.PI / 2 + * @memberOf me.ParticleEmitterSettings + */ + angle : Math.PI / 2, + + /** + * Variation in the start angle for particle launch in Radians.
+ * @public + * @type Number + * @name angleVariation + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + angleVariation : 0, + + /** + * Minimum time each particle lives once it is emitted in ms.
+ * @public + * @type Number + * @name minLife + * @default 1000 + * @memberOf me.ParticleEmitterSettings + */ + minLife : 1000, + + /** + * Maximum time each particle lives once it is emitted in ms.
+ * @public + * @type Number + * @name maxLife + * @default 3000 + * @memberOf me.ParticleEmitterSettings + */ + maxLife : 3000, + + /** + * Start speed of particles.
+ * @public + * @type Number + * @name speed + * @default 2 + * @memberOf me.ParticleEmitterSettings + */ + speed : 2, + + /** + * Variation in the start speed of particles.
+ * @public + * @type Number + * @name speedVariation + * @default 1 + * @memberOf me.ParticleEmitterSettings + */ + speedVariation : 1, + + /** + * Minimum start rotation for particles sprites in Radians.
+ * @public + * @type Number + * @name minRotation + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + minRotation : 0, + + /** + * Maximum start rotation for particles sprites in Radians.
+ * @public + * @type Number + * @name maxRotation + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + maxRotation : 0, + + /** + * Minimum start scale ratio for particles (1 = no scaling).
+ * @public + * @type Number + * @name minStartScale + * @default 1 + * @memberOf me.ParticleEmitterSettings + */ + minStartScale : 1, + + /** + * Maximum start scale ratio for particles (1 = no scaling).
+ * @public + * @type Number + * @name maxStartScale + * @default 1 + * @memberOf me.ParticleEmitterSettings + */ + maxStartScale : 1, + + /** + * Minimum end scale ratio for particles.
+ * @public + * @type Number + * @name minEndScale + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + minEndScale : 0, + + /** + * Maximum end scale ratio for particles.
+ * @public + * @type Number + * @name maxEndScale + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + maxEndScale : 0, + + /** + * Vertical force (Gravity) for each particle.
+ * @public + * @type Number + * @name gravity + * @default 0 + * @memberOf me.ParticleEmitterSettings + * @see me.sys.gravity + */ + gravity : 0, + + /** + * Horizontal force (like a Wind) for each particle.
+ * @public + * @type Number + * @name wind + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + wind : 0, + + /** + * Update the rotation of particle in accordance the particle trajectory.
+ * The particle sprite should aim at zero angle (draw from left to right).
+ * Override the particle minRotation and maxRotation.
+ * @public + * @type Boolean + * @name followTrajectory + * @default false + * @memberOf me.ParticleEmitterSettings + */ + followTrajectory : false, + + /** + * Enable the Texture Additive by canvas composite operation (lighter).
+ * WARNING: Composite Operation may decreases performance!.
+ * @public + * @type Boolean + * @name textureAdditive + * @default false + * @memberOf me.ParticleEmitterSettings + */ + textureAdditive : false, + + /** + * Update particles only in the viewport, remove it when out of viewport.
+ * @public + * @type Boolean + * @name onlyInViewport + * @default true + * @memberOf me.ParticleEmitterSettings + */ + onlyInViewport : true, + + /** + * Render particles in screen space.
+ * @public + * @type Boolean + * @name floating + * @default false + * @memberOf me.ParticleEmitterSettings + */ + floating : false, + + /** + * Maximum number of particles launched each time in this emitter (used only if emitter is Stream).
+ * @public + * @type Number + * @name maxParticles + * @default 10 + * @memberOf me.ParticleEmitterSettings + */ + maxParticles : 10, + + /** + * How often a particle is emitted in ms (used only if emitter is Stream).
+ * Necessary that value is greater than zero.
+ * @public + * @type Number + * @name frequency + * @default 100 + * @memberOf me.ParticleEmitterSettings + */ + frequency : 100, + + /** + * Duration that the emitter releases particles in ms (used only if emitter is Stream).
+ * After this period, the emitter stop the launch of particles.
+ * @public + * @type Number + * @name duration + * @default Infinity + * @memberOf me.ParticleEmitterSettings + */ + duration : Infinity, + + /** + * Skip n frames after updating the particle system once.
+ * This can be used to reduce the performance impact of emitters with many particles.
+ * @public + * @type Number + * @name framesToSkip + * @default 0 + * @memberOf me.ParticleEmitterSettings + */ + framesToSkip : 0 + }; + + /** + * Particle Emitter Object. + * @class + * @extends Rect + * @memberOf me + * @constructor + * @param {Number} x x-position of the particle emitter + * @param {Number} y y-position of the particle emitter + * @param {object} settings An object containing the settings for the particle emitter. See {@link me.ParticleEmitterSettings} + * @example + * + * // Create a basic emitter at position 100, 100 + * var emitter = new me.ParticleEmitter(100, 100); + * + * // Adjust the emitter properties + * emitter.totalParticles = 200; + * emitter.minLife = 1000; + * emitter.maxLife = 3000; + * emitter.z = 10; + * + * // Add the emitter to the game world + * me.game.world.addChild(emitter); + * + * // Launch all particles one time and stop, like a explosion + * emitter.burstParticles(); + * + * // Launch constantly the particles, like a fountain + * emitter.streamParticles(); + * + * // At the end, remove emitter from the game world + * // call this in onDestroyEvent function + * me.game.world.removeChild(emitter); + * + */ + me.ParticleEmitter = me.Rect.extend( + /** @scope me.ParticleEmitter.prototype */ + { + /** + * @ignore + */ + init: function (x, y, settings) { + // Emitter is Stream, launch particles constantly + /** @ignore */ + this._stream = false; + + // Frequency timer (in ms) for emitter launch new particles + // used only in stream emitter + /** @ignore */ + this._frequencyTimer = 0; + + // Time of live (in ms) for emitter launch new particles + // used only in stream emitter + /** @ignore */ + this._durationTimer = 0; + + // Emitter is emitting particles + /** @ignore */ + this._enabled = false; + // Emitter will always update + this.isRenderable = false; + // call the super constructor + me.Rect.prototype.init.apply(this, + [x, y, + Infinity, + Infinity] + ); + + // don't sort the particles by z-index + this.autoSort = false; + + this.container = new me.ParticleContainer(this); + + /** + * @ignore + */ + Object.defineProperty(this.pos, "z", { + /** + * @ignore + */ + get : (function () { return this.container.pos.z; }).bind(this), + /** + * @ignore + */ + set : (function (value) { this.container.pos.z = value; }).bind(this), + enumerable : true, + configurable : true + }); + + /** + * Floating property for particles, value is forwarded to the particle container
+ * @type Boolean + * @name floating + * @memberOf me.ParticleEmitter + */ + Object.defineProperty(this, "floating", { + /** + * @ignore + */ + get : function () { return this.container.floating; }, + /** + * @ignore + */ + set : function (value) { this.container.floating = value; }, + enumerable : true, + configurable : true + }); + + // Reset the emitter to defaults + this.reset(settings); + }, + + /** + * @ignore + */ + onActivateEvent: function() { + this.ancestor.addChild(this.container); + this.container.pos.z = this.pos.z; + if (!this.ancestor.autoSort) { + this.ancestor.sort(); + } + }, + + /** + * @ignore + */ + onDeactivateEvent: function() { + if (this.ancestor.hasChild(this.container)) { + this.ancestor.removeChildNow(this.container); + } + }, + + /** + * @ignore + */ + destroy: function () { + this.reset(); + }, + + /** + * returns a random point inside the bounds x axis of this emitter + * @name getRandomPointX + * @memberOf me.ParticleEmitter + * @function + * @return {Number} + */ + getRandomPointX: function () { + return this.pos.x + (0).randomFloat(this.width); + }, + + /** + * returns a random point inside the bounds y axis of this emitter + * @name getRandomPointY + * @memberOf me.ParticleEmitter + * @function + * @return {Number} + */ + getRandomPointY: function () { + return this.pos.y + (0).randomFloat(this.height); + }, + + /** + * Reset the emitter with default values.
+ * @function + * @param {Object} settings [optional] object with emitter settings. See {@link me.ParticleEmitterSettings} + * @name reset + * @memberOf me.ParticleEmitter + */ + reset: function (settings) { + // check if settings exists and create a dummy object if necessary + settings = settings || {}; + var defaults = me.ParticleEmitterSettings; + + var width = (typeof settings.width === "number") ? settings.width : defaults.width; + var height = (typeof settings.height === "number") ? settings.height : defaults.height; + this.resize(width, height); + + Object.assign(this, defaults, settings); + + // reset particle container values + this.container.destroy(); + }, + + // Add count particles in the game world + /** @ignore */ + addParticles: function (count) { + for (var i = 0; i < ~~count; i++) { + // Add particle to the container + var particle = me.pool.pull("me.Particle", this); + this.container.addChild(particle); + } + }, + + /** + * Emitter is of type stream and is launching particles
+ * @function + * @returns {Boolean} Emitter is Stream and is launching particles + * @name isRunning + * @memberOf me.ParticleEmitter + */ + isRunning: function () { + return this._enabled && this._stream; + }, + + /** + * Launch particles from emitter constantly
+ * Particles example: Fountains + * @param {Number} duration [optional] time that the emitter releases particles in ms + * @function + * @name streamParticles + * @memberOf me.ParticleEmitter + */ + streamParticles: function (duration) { + this._enabled = true; + this._stream = true; + this.frequency = Math.max(this.frequency, 1); + this._durationTimer = (typeof duration === "number") ? duration : this.duration; + }, + + /** + * Stop the emitter from generating new particles (used only if emitter is Stream)
+ * @function + * @name stopStream + * @memberOf me.ParticleEmitter + */ + stopStream: function () { + this._enabled = false; + }, + + /** + * Launch all particles from emitter and stop
+ * Particles example: Explosions
+ * @param {Number} total [optional] number of particles to launch + * @function + * @name burstParticles + * @memberOf me.ParticleEmitter + */ + burstParticles: function (total) { + this._enabled = true; + this._stream = false; + this.addParticles((typeof total === "number") ? total : this.totalParticles); + this._enabled = false; + }, + + /** + * @ignore + */ + update: function (dt) { + // Launch new particles, if emitter is Stream + if ((this._enabled) && (this._stream)) { + // Check if the emitter has duration set + if (this._durationTimer !== Infinity) { + this._durationTimer -= dt; + + if (this._durationTimer <= 0) { + this.stopStream(); + return false; + } + } + + // Increase the emitter launcher timer + this._frequencyTimer += dt; + + // Check for new particles launch + var particlesCount = this.container.children.length; + if ((particlesCount < this.totalParticles) && (this._frequencyTimer >= this.frequency)) { + if ((particlesCount + this.maxParticles) <= this.totalParticles) { + this.addParticles(this.maxParticles); + } + else { + this.addParticles(this.totalParticles - particlesCount); + } + + this._frequencyTimer = 0; + } + } + return true; + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * Particle Container Object. + * @class + * @extends me.Container + * @memberOf me + * @constructor + * @param {me.ParticleEmitter} emitter the emitter which owns this container + */ + me.ParticleContainer = me.Container.extend( + /** @scope ParticleContainer */ + { + /** + * @ignore + */ + init: function (emitter) { + // cache a reference to the viewport to use as our bounding box + this._viewport = me.game.viewport; + + // call the super constructor + me.Container.prototype.init.apply(this); + + // don't sort the particles by z-index + this.autoSort = false; + + // count the updates + this._updateCount = 0; + + // internally store how much time was skipped when frames are skipped + this._dt = 0; + + // cache the emitter for later use + this._emitter = emitter; + }, + + /** + * @ignore + */ + getBounds : function () { + return this._viewport; + }, + + /** + * @ignore + */ + update: function (dt) { + // skip frames if necessary + if (++this._updateCount > this._emitter.framesToSkip) { + this._updateCount = 0; + } + if (this._updateCount > 0) { + this._dt += dt; + return false; + } + + // apply skipped delta time + dt += this._dt; + this._dt = 0; + + // Update particles and remove them if they are dead + var viewport = me.game.viewport; + for (var i = this.children.length - 1; i >= 0; --i) { + var particle = this.children[i]; + particle.isRenderable = true; + // particle.inViewport = viewport.isVisible(particle); + particle.inViewport = this.floating || ( + particle.pos.x < viewport.pos.x + viewport.width && + viewport.pos.x < particle.pos.x + particle.width && + particle.pos.y < viewport.pos.y + viewport.height && + viewport.pos.y < particle.pos.y + particle.height + ); + if (!particle.update(dt)) { + this.removeChildNow(particle); + } + } + return true; + }, + + /** + * @ignore + */ + draw : function (renderer, rect) { + if (this.children.length > 0) { + var context = renderer.getContext(), + gco; + // Check for additive draw + if (this._emitter.textureAdditive) { + gco = context.globalCompositeOperation; + context.globalCompositeOperation = "lighter"; + } + + me.Container.prototype.draw.apply(this, [renderer, rect]); + + // Restore globalCompositeOperation + if (this._emitter.textureAdditive) { + context.globalCompositeOperation = gco; + } + } + } + }); +})(); + +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + */ +(function () { + /** + * Single Particle Object. + * @class + * @extends me.Renderable + * @memberOf me + * @constructor + * @param {me.ParticleEmitter} particle emitter + */ + me.Particle = me.Renderable.extend( + /** @scope me.Particle.prototype */ + { + /** + * @ignore + */ + init : function (emitter) { + // Call the super constructor + me.Renderable.prototype.init.apply(this, [ + emitter.getRandomPointX(), + emitter.getRandomPointY(), + emitter.image.width, + emitter.image.height + ]); + + // Particle will always update + this.alwaysUpdate = true; + + // Particle will not act as a rednerable + // FIXME: This is probably not needed. It's a hack that tries to + // workaround performance issues within container. + this.isRenderable = false; + + // Cache the image reference + this.image = emitter.image; + + // Set the start particle Angle and Speed as defined in emitter + var angle = emitter.angle + ((emitter.angleVariation > 0) ? ((0).randomFloat(2) - 1) * emitter.angleVariation : 0); + var speed = emitter.speed + ((emitter.speedVariation > 0) ? ((0).randomFloat(2) - 1) * emitter.speedVariation : 0); + + // Set the start particle Velocity + this.vel = new me.Vector2d(speed * Math.cos(angle), -speed * Math.sin(angle)); + + // Set the start particle Time of Life as defined in emitter + this.life = emitter.minLife.randomFloat(emitter.maxLife); + this.startLife = this.life; + + // Set the start and end particle Scale as defined in emitter + // clamp the values as minimum and maximum scales range + this.startScale = emitter.minStartScale.randomFloat( + emitter.maxStartScale + ).clamp(emitter.minStartScale, emitter.maxStartScale); + this.endScale = emitter.minEndScale.randomFloat( + emitter.maxEndScale + ).clamp(emitter.minEndScale, emitter.maxEndScale); + + // Set the particle Gravity and Wind (horizontal gravity) as defined in emitter + this.gravity = emitter.gravity; + this.wind = emitter.wind; + + // Set if the particle update the rotation in accordance the trajectory + this.followTrajectory = emitter.followTrajectory; + + // Set if the particle update only in Viewport + this.onlyInViewport = emitter.onlyInViewport; + + // Set the particle Z Order + this.pos.z = emitter.z; + + // cache inverse of the expected delta time + this._deltaInv = me.sys.fps / 1000; + + // Set the start particle rotation as defined in emitter + // if the particle not follow trajectory + if (!emitter.followTrajectory) { + this.angle = emitter.minRotation.randomFloat(emitter.maxRotation); + } + }, + + /** + * Update the Particle
+ * This is automatically called by the game manager {@link me.game} + * @name update + * @memberOf me.Particle + * @function + * @ignore + * @param {Number} dt time since the last update in milliseconds + */ + update : function (dt) { + // move things forward independent of the current frame rate + var skew = dt * this._deltaInv; + + // Decrease particle life + this.life = this.life > dt ? this.life - dt : 0; + + // Calculate the particle Age Ratio + var ageRatio = this.life / this.startLife; + + // Resize the particle as particle Age Ratio + var scale = this.startScale; + if (this.startScale > this.endScale) { + scale *= ageRatio; + scale = (scale < this.endScale) ? this.endScale : scale; + } + else if (this.startScale < this.endScale) { + scale /= ageRatio; + scale = (scale > this.endScale) ? this.endScale : scale; + } + + // Set the particle opacity as Age Ratio + this.alpha = ageRatio; + + // Adjust the particle velocity + this.vel.x += this.wind * skew; + this.vel.y += this.gravity * skew; + + // If necessary update the rotation of particle in accordance the particle trajectory + var angle = this.followTrajectory ? Math.atan2(this.vel.y, this.vel.x) : this.angle; + + this.pos.x += this.vel.x * skew; + this.pos.y += this.vel.y * skew; + + // Update particle transform + this.currentTransform.setTransform( + scale, 0, 0, + 0, scale, 0, + this.pos.x, this.pos.y, 1 + ).rotate(angle); + + // Return true if the particle is not dead yet + return (this.inViewport || !this.onlyInViewport) && (this.life > 0); + }, + + /** + * @ignore + */ + draw : function (renderer) { + renderer.save(); + + // particle alpha value + renderer.setGlobalAlpha(renderer.globalAlpha() * this.alpha); + + // translate to the defined anchor point and scale it + renderer.transform(this.currentTransform); + + var w = this.width, h = this.height; + renderer.drawImage( + this.image, + 0, 0, + w, h, + -w / 2, -h / 2, + w, h + ); + + renderer.restore(); + } + }); + + + /*---------------------------------------------------------*/ + // END END END + /*---------------------------------------------------------*/ +})(window); diff --git a/Games/Contra-Shooter/lib/plugins/debug/debugPanel.js b/Games/Contra-Shooter/lib/plugins/debug/debugPanel.js new file mode 100644 index 0000000000..2dee306646 --- /dev/null +++ b/Games/Contra-Shooter/lib/plugins/debug/debugPanel.js @@ -0,0 +1,701 @@ +/* + * MelonJS Game Engine + * Copyright (C) 2011 - 2015, Olivier Biot, Jason Oster, Aaron McLeod + * http://www.melonjs.org + * + * a simple debug panel plugin + * usage : me.plugin.register.defer(this, me.debug.Panel, "debug"); + * + * you can then use me.plugins.debug.show() or me.plugins.debug.hide() + * to show or hide the panel, or press respectively the "S" and "H" keys. + * + * note : + * Heap Memory information is available under Chrome when using + * the "--enable-memory-info" parameter to launch Chrome + */ + +(function () { + + // ensure that me.debug is defined + me.debug = me.debug || {}; + + var DEBUG_HEIGHT = 50; + + var Counters = me.Object.extend({ + init : function (stats) { + this.stats = {}; + this.reset(stats); + }, + + reset : function (stats) { + var self = this; + (stats || Object.keys(this.stats)).forEach(function (stat) { + self.stats[stat] = 0; + }); + }, + + inc : function (stat, value) { + this.stats[stat] += (value || 1); + }, + + get : function (stat) { + return this.stats[stat]; + } + }); + + // embedded bitmap font data + var fontDataSource = + "info face=\"PressStart2P\" size=10 bold=0 italic=0 charset= unicode= stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=0,0 outline=0\n" + + "common lineHeight=10 base=10 scaleW=128 scaleH=128 pages=1 packed=0\n" + + "page id=0 file=\"PressStart2P.png\"\n" + + "chars count=95\n" + + "char id=32 x=1 y=1 width=0 height=0 xoffset=0 yoffset=10 xadvance=10 page=0 chnl=15\n" + + "char id=33 x=1 y=2 width=5 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=34 x=1 y=13 width=8 height=5 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=35 x=7 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=36 x=1 y=19 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=37 x=1 y=30 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=38 x=1 y=41 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=39 x=10 y=12 width=4 height=5 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=40 x=1 y=52 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=41 x=1 y=63 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=42 x=1 y=74 width=10 height=8 xoffset=0 yoffset=1 xadvance=10 page=0 chnl=15\n" + + "char id=43 x=8 y=52 width=9 height=8 xoffset=1 yoffset=1 xadvance=10 page=0 chnl=15\n" + + "char id=44 x=8 y=61 width=5 height=5 xoffset=1 yoffset=6 xadvance=10 page=0 chnl=15\n" + + "char id=45 x=8 y=67 width=9 height=2 xoffset=1 yoffset=4 xadvance=10 page=0 chnl=15\n" + + "char id=46 x=14 y=61 width=4 height=4 xoffset=3 yoffset=6 xadvance=10 page=0 chnl=15\n" + + "char id=47 x=12 y=18 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=48 x=18 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=49 x=12 y=29 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=50 x=12 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=51 x=22 y=29 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=52 x=23 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=53 x=29 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=54 x=1 y=83 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=55 x=1 y=94 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=56 x=1 y=105 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=57 x=1 y=116 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=58 x=18 y=51 width=4 height=8 xoffset=3 yoffset=1 xadvance=10 page=0 chnl=15\n" + + "char id=59 x=12 y=70 width=5 height=9 xoffset=1 yoffset=1 xadvance=10 page=0 chnl=15\n" + + "char id=60 x=12 y=80 width=8 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=61 x=23 y=23 width=10 height=5 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=62 x=18 y=66 width=8 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=63 x=23 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=64 x=33 y=29 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=65 x=23 y=51 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=66 x=34 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=67 x=40 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=68 x=12 y=91 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=69 x=21 y=77 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=70 x=27 y=62 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=71 x=34 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=72 x=34 y=51 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=73 x=44 y=23 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=74 x=45 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=75 x=51 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=76 x=12 y=102 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=77 x=12 y=113 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=78 x=22 y=102 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=79 x=23 y=88 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=80 x=32 y=73 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=81 x=38 y=62 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=82 x=23 y=113 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=83 x=33 y=99 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=84 x=34 y=84 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=85 x=43 y=73 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=86 x=34 y=110 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=87 x=44 y=84 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=88 x=44 y=95 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=89 x=45 y=106 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=90 x=45 y=117 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=91 x=45 y=34 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=92 x=45 y=45 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=93 x=52 y=34 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=94 x=34 y=23 width=8 height=4 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=95 x=34 y=121 width=10 height=2 xoffset=0 yoffset=9 xadvance=10 page=0 chnl=15\n" + + "char id=96 x=15 y=12 width=4 height=4 xoffset=4 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=97 x=54 y=23 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=98 x=56 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=99 x=62 y=1 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=100 x=49 y=56 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=101 x=56 y=45 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=102 x=59 y=31 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=103 x=54 y=67 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=104 x=60 y=54 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=105 x=67 y=42 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=106 x=67 y=9 width=8 height=11 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=107 x=69 y=21 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=108 x=76 y=1 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=109 x=76 y=12 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=110 x=69 y=32 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=111 x=86 y=1 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=112 x=97 y=1 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=113 x=108 y=1 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=114 x=87 y=10 width=9 height=8 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=115 x=97 y=11 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=116 x=108 y=11 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=117 x=87 y=19 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=118 x=98 y=19 width=9 height=8 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=119 x=80 y=27 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=120 x=108 y=22 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=121 x=91 y=28 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=122 x=80 y=35 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=123 x=118 y=11 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=124 x=102 y=28 width=4 height=10 xoffset=4 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=125 x=119 y=22 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" + + "char id=126 x=91 y=38 width=10 height=5 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" + + "char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15"; + + var fontImageSource = "data:image/png;base64,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"; + + var DebugPanel = me.Renderable.extend({ + /** @private */ + init : function (showKey, hideKey) { + // call the super constructor + this._super(me.Renderable, "init", [ 0, 0, me.game.viewport.width, DEBUG_HEIGHT ]); + + // minimum melonJS version expected + this.version = "4.0.0"; + + // to hold the debug options + // clickable rect area + this.area = {}; + + // Useful counters + this.counters = new Counters([ + "shapes", + "sprites", + "velocity", + "bounds", + "children" + ]); + + // for z ordering + // make it ridiculously high + this.pos.z = Infinity; + + // visibility flag + this.visible = false; + + // frame update time in ms + this.frameUpdateTime = 0; + + // frame draw time in ms + this.frameDrawTime = 0; + + // set the object GUID value + this.GUID = "debug-" + me.utils.createGUID(); + + // set the object entity name + this.name = "me.debugPanel"; + + // persistent + this.isPersistent = true; + + // a floating object + this.floating = true; + + // renderable + this.isRenderable = true; + + // always update, even when not visible + this.alwaysUpdate = true; + + // WebGL/Canvas compatibility + this.canvas = me.video.createCanvas(this.width, this.height, true); + + // create a default font, with fixed char width + this.font_size = 10; + this.mod = 2; + if (this.width < 500) { + this.font_size = 7; + this.mod = this.mod * (this.font_size / 10); + } + + // create the bitmapfont + var fontImage = new Image(); + fontImage.src = fontImageSource; + + this.font = new me.BitmapFont( + fontDataSource, + fontImage + ); + + // free static ressources + fontImageSource = null; + fontDataSource = null; + + // clickable areas + var size = 10 * this.mod; + this.area.renderHitBox = new me.Rect(250, 2, size, size); + this.area.renderVelocity = new me.Rect(250, 17, size, size); + this.area.renderQuadTree = new me.Rect(410, 2, size, size); + + // some internal string/length + this.help_str = "(s)how/(h)ide"; + this.help_str_len = this.font.measureText(me.video.renderer, this.help_str).width; + this.fps_str_len = this.font.measureText(me.video.renderer, "00/00 fps").width; + this.memoryPositionX = 325 * this.mod; + + // enable the FPS counter + me.debug.displayFPS = true; + + var self = this; + + // add some keyboard shortcuts + this.showKey = showKey || me.input.KEY.S; + this.hideKey = hideKey || me.input.KEY.H; + this.keyHandler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode) { + if (keyCode === self.showKey) { + me.plugins.debugPanel.show(); + } else if (keyCode === self.hideKey) { + me.plugins.debugPanel.hide(); + } + }); + + // resize the panel if the browser is resized + me.event.subscribe(me.event.VIEWPORT_ONRESIZE, function (w) { + self.resize(w, DEBUG_HEIGHT); + }); + + //patch patch patch ! + this.patchSystemFn(); + }, + + /** + * patch system fn to draw debug information + */ + patchSystemFn : function () { + + // add a few new debug flag (if not yet defined) + me.debug.renderHitBox = me.debug.renderHitBox || me.game.HASH.hitbox || false; + me.debug.renderVelocity = me.debug.renderVelocity || me.game.HASH.velocity || false; + me.debug.renderQuadTree = me.debug.renderQuadTree || me.game.HASH.quadtree || false; + + var _this = this; + var bounds = new me.Rect(0, 0, 0, 0); + + // patch timer.js + me.plugin.patch(me.timer, "update", function (dt) { + // call the original me.timer.update function + this._patched(dt); + + // call the FPS counter + me.timer.countFPS(); + }); + + // patch me.game.update + me.plugin.patch(me.game, "update", function (dt) { + var frameUpdateStartTime = window.performance.now(); + + this._patched(dt); + + // calculate the update time + _this.frameUpdateTime = window.performance.now() - frameUpdateStartTime; + }); + + // patch me.game.draw + me.plugin.patch(me.game, "draw", function () { + var frameDrawStartTime = window.performance.now(); + + _this.counters.reset(); + + this._patched(); + + // calculate the drawing time + _this.frameDrawTime = window.performance.now() - frameDrawStartTime; + }); + + // patch sprite.js + me.plugin.patch(me.Sprite, "draw", function (renderer) { + // call the original me.Sprite.draw function + this._patched(renderer); + + if (!_this.visible) { + // don't do anything else if the panel is hidden + return; + } + + // increment the sprites counter + _this.counters.inc("sprites"); + + // draw the sprite rectangle + if (me.debug.renderHitBox) { + var bounds = this.getBounds(); + var x = - (this.anchorPoint.x * this.width) - ((bounds.width - this.width) / 2); + var y = - (this.anchorPoint.y * this.height) - ((bounds.height - this.height) / 2); + + renderer.save(); + renderer.setColor("green"); + renderer.translate(x, y); + renderer.drawShape(bounds); + renderer.restore(); + } + }); + + /* + // patch font.js + me.plugin.patch(me.Font, "draw", function (renderer, text, x, y) { + // call the original me.Sprite.draw function + this._patched(renderer, text, x, y); + + // draw the font rectangle + if (me.debug.renderHitBox) { + renderer.save(); + renderer.setColor("orange"); + renderer.drawShape(this.getBounds()); + _this.counters.inc("bounds"); + renderer.restore(); + } + }); + + // patch font.js + me.plugin.patch(me.Font, "drawStroke", function (renderer, text, x, y) { + // call the original me.Sprite.draw function + this._patched(renderer, text, x, y); + + // draw the font rectangle + if (me.debug.renderHitBox) { + renderer.save(); + renderer.setColor("orange"); + renderer.drawShape(this.getBounds()); + _this.counters.inc("bounds"); + renderer.restore(); + } + }); + */ + + // patch entities.js + me.plugin.patch(me.Entity, "draw", function (renderer) { + // call the original me.Entity.draw function + this._patched(renderer); + + if (!_this.visible) { + // don't do anything else if the panel is hidden + return; + } + + // increment the bounds counter + _this.counters.inc("bounds"); + + // check if debug mode is enabled + if (me.debug.renderHitBox) { + renderer.save(); + renderer.setLineWidth(1); + + // draw the bounding rect shape + renderer.setColor("orange"); + bounds.copy(this.getBounds()); + bounds.pos.sub(this.ancestor._absPos); + renderer.drawShape(bounds); + + // draw all defined shapes + renderer.setColor("red"); + renderer.translate(this.pos.x, this.pos.y); + } + for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) { + if (me.debug.renderHitBox) { + renderer.drawShape(shape); + } + _this.counters.inc("shapes"); + } + + if (me.debug.renderHitBox) { + renderer.restore(); + } + + if (me.debug.renderVelocity && (this.body.vel.x || this.body.vel.y)) { + bounds.copy(this.getBounds()); + bounds.pos.sub(this.ancestor._absPos); + // draw entity current velocity + var x = ~~(bounds.pos.x + (bounds.width / 2)); + var y = ~~(bounds.pos.y + (bounds.height / 2)); + + renderer.save(); + renderer.setLineWidth(1); + + renderer.setColor("blue"); + renderer.translate(x, y); + renderer.strokeLine(0, 0, ~~(this.body.vel.x * (bounds.width / 2)), ~~(this.body.vel.y * (bounds.height / 2))); + _this.counters.inc("velocity"); + + renderer.restore(); + } + }); + + // patch container.js + me.plugin.patch(me.Container, "draw", function (renderer, rect) { + // call the original me.Container.draw function + this._patched(renderer, rect); + + // check if debug mode is enabled + if (!_this.visible) { + // don't do anything else if the panel is hidden + return; + } + + // increment counters + _this.counters.inc("bounds"); + _this.counters.inc("children"); + + if (me.debug.renderHitBox) { + renderer.save(); + renderer.setLineWidth(1); + + // draw the bounding rect shape + renderer.setColor("orange"); + bounds.copy(this.getBounds()); + if (this.ancestor) { + bounds.pos.sub(this.ancestor._absPos); + } + renderer.drawShape(bounds); + + // draw the children bounding rect shape + renderer.setColor("purple"); + bounds.copy(this.childBounds); + if (this.ancestor) { + bounds.pos.sub(this.ancestor._absPos); + } + renderer.drawShape(bounds); + + renderer.restore(); + } + }); + }, + + /** + * show the debug panel + */ + show : function () { + if (!this.visible) { + // add the debug panel to the game world + me.game.world.addChild(this, Infinity); + // register a mouse event for the checkboxes + me.input.registerPointerEvent("pointerdown", this, this.onClick.bind(this)); + // mark it as visible + this.visible = true; + // force repaint + me.game.repaint(); + } + }, + + /** + * hide the debug panel + */ + hide : function () { + if (this.visible) { + // release the mouse event for the checkboxes + me.input.releasePointerEvent("pointerdown", this); + // remove the debug panel from the game world + me.game.world.removeChild(this, true); + // mark it as invisible + this.visible = false; + // force repaint + me.game.repaint(); + } + }, + + + /** @private */ + update : function () { + return this.visible; + }, + + /** @private */ + onClick : function (e) { + // check the clickable areas + if (this.area.renderHitBox.containsPoint(e.gameX, e.gameY)) { + me.debug.renderHitBox = !me.debug.renderHitBox; + } else if (this.area.renderVelocity.containsPoint(e.gameX, e.gameY)) { + // does nothing for now, since velocity is + // rendered together with hitboxes (is a global debug flag required?) + me.debug.renderVelocity = !me.debug.renderVelocity; + } else if (this.area.renderQuadTree.containsPoint(e.gameX, e.gameY)) { + me.debug.renderQuadTree = !me.debug.renderQuadTree; + } + // force repaint + me.game.repaint(); + }, + + /** @private */ + drawQuadTreeNode : function (renderer, node) { + var bounds = node.bounds; + + // draw the current bounds + if (node.nodes.length === 0) { + // cap the alpha value to 0.4 maximum + var _alpha = (node.objects.length * 0.4) / me.collision.maxChildren; + if (_alpha > 0.0) { + renderer.save(); + renderer.setColor("rgba(255,0,0," + _alpha + ")"); + renderer.fillRect(bounds.pos.x, bounds.pos.y, bounds.width, bounds.height); + renderer.restore(); + } + } else { + //has subnodes? drawQuadtree them! + for (var i = 0; i < node.nodes.length; i++) { + this.drawQuadTreeNode(renderer, node.nodes[i]); + } + } + }, + + /** @private */ + drawQuadTree : function (renderer) { + // save the current globalAlpha value + var _alpha = renderer.globalAlpha(); + var x = me.game.viewport.pos.x; + var y = me.game.viewport.pos.y; + + renderer.translate(-x, -y); + + this.drawQuadTreeNode(renderer, me.collision.quadTree); + + renderer.translate(x, y); + + renderer.setGlobalAlpha(_alpha); + }, + + /** @private */ + drawMemoryGraph : function (renderer, endX) { + if (window.performance && window.performance.memory) { + var usedHeap = Number.prototype.round(window.performance.memory.usedJSHeapSize / 1048576, 2); + var totalHeap = Number.prototype.round(window.performance.memory.totalJSHeapSize / 1048576, 2); + var maxLen = ~~(endX - this.memoryPositionX - 5); + var len = maxLen * (usedHeap / totalHeap); + + renderer.setColor("#0065AD"); + renderer.fillRect(this.memoryPositionX, 0, maxLen, 20); + renderer.setColor("#3AA4F0"); + renderer.fillRect(this.memoryPositionX + 1, 1, len - 1, 17); + + this.font.draw(renderer, "Heap : " + usedHeap + "/" + totalHeap + " MB", this.memoryPositionX + 5, 2 * this.mod); + } else { + // Heap Memory information not available + this.font.draw(renderer, "Heap : ??/?? MB", this.memoryPositionX, 2 * this.mod); + } + }, + + /** @private */ + draw : function (renderer) { + renderer.save(); + + // draw the QuadTree (before the panel) + if (me.debug.renderQuadTree === true) { + this.drawQuadTree(renderer); + } + + // draw the panel + renderer.setGlobalAlpha(0.5); + renderer.setColor("black"); + renderer.fillRect( + this.left, this.top, + this.width, this.height + ); + renderer.setGlobalAlpha(1.0); + renderer.setColor("white"); + + this.font.textAlign = "left"; + + this.font.draw(renderer, "#objects : " + me.game.world.children.length, 5 * this.mod, 2 * this.mod); + this.font.draw(renderer, "#draws : " + me.game.world.drawCount, 5 * this.mod, 10 * this.mod); + + // debug checkboxes + this.font.draw(renderer, "?hitbox [" + (me.debug.renderHitBox ? "x" : " ") + "]", 75 * this.mod, 2 * this.mod); + this.font.draw(renderer, "?velocity [" + (me.debug.renderVelocity ? "x" : " ") + "]", 75 * this.mod, 10 * this.mod); + + this.font.draw(renderer, "?QuadTree [" + (me.debug.renderQuadTree ? "x" : " ") + "]", 150 * this.mod, 2 * this.mod); + + // draw the update duration + this.font.draw(renderer, "Update : " + this.frameUpdateTime.toFixed(2) + " ms", 225 * this.mod, 2 * this.mod); + // draw the draw duration + this.font.draw(renderer, "Draw : " + this.frameDrawTime.toFixed(2) + " ms", 225 * this.mod, 10 * this.mod); + + + // Draw color code hints (not supported with bitmapfont) + //this.font.fillStyle.copy("red"); + this.font.draw(renderer, "Shapes : " + this.counters.get("shapes"), 5 * this.mod, 17 * this.mod); + + //this.font.fillStyle.copy("green"); + this.font.draw(renderer, "Sprites : " + this.counters.get("sprites"), 75 * this.mod, 17 * this.mod); + + //this.font.fillStyle.copy("blue"); + this.font.draw(renderer, "Velocity : " + this.counters.get("velocity"), 150 * this.mod, 17 * this.mod); + + //this.font.fillStyle.copy("orange"); + this.font.draw(renderer, "Bounds : " + this.counters.get("bounds"), 225 * this.mod, 17 * this.mod); + + //this.font.fillStyle.copy("purple"); + this.font.draw(renderer, "Children : " + this.counters.get("children"), 325 * this.mod, 17 * this.mod); + + // Reset font style + //this.font.setFont("courier", this.font_size, "white"); + + // draw the memory heap usage + var endX = this.width - 5; + this.drawMemoryGraph(renderer, endX - this.help_str_len); + + this.font.textAlign = "right"; + + // some help string + this.font.draw(renderer, this.help_str, endX, 17 * this.mod); + + //fps counter + var fps_str = me.timer.fps + "/" + me.sys.fps + " fps"; + this.font.draw(renderer, fps_str, endX, 2 * this.mod); + + renderer.restore(); + }, + + /** @private */ + onDestroyEvent : function () { + // hide the panel + this.hide(); + // unbind keys event + me.input.unbindKey(me.input.KEY.S); + me.input.unbindKey(me.input.KEY.H); + me.event.unsubscribe(this.keyHandler); + } + }); + + /** + * @class + * @public + * @extends me.plugin.Base + * @memberOf me + * @constructor + */ + me.debug.Panel = me.plugin.Base.extend( + /** @scope me.debug.Panel.prototype */ + { + /** @private */ + init : function (showKey, hideKey) { + // call the super constructor + this._super(me.plugin.Base, "init"); + this.panel = new DebugPanel(showKey, hideKey); + + // if "#debug" is present in the URL + if (me.game.HASH.debug === true) { + this.show(); + } // else keep it hidden + }, + + /** @private */ + show : function () { + this.panel.show(); + }, + + /** @private */ + hide : function () { + this.panel.hide(); + } + }); + + // automatically register the debug panel + window.onReady(function () { + me.plugin.register.defer(this, me.debug.Panel, "debugPanel"); + }); + + /*---------------------------------------------------------*/ + // END END END + /*---------------------------------------------------------*/ +})(); diff --git a/Games/Contra-Shooter/main.js b/Games/Contra-Shooter/main.js new file mode 100644 index 0000000000..9c0bdceda6 --- /dev/null +++ b/Games/Contra-Shooter/main.js @@ -0,0 +1,48 @@ +const electron = require('electron'); +const app = electron.app; // Module to control application life. +const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. + +const path = require('path'); +const url = require('url'); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +let mainWindow; + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + if (process.platform != 'darwin') + app.quit(); +}); + +function createWindow() { + // Create the browser window. + mainWindow = new BrowserWindow({ width: 960, height: 660 }); + + // and load the index.html of the app. + mainWindow.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })); + + // Emitted when the window is closed. + mainWindow.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null; + }); +} + +// This method will be called when atom-shell has done everything +// initialization and ready for creating browser windows. +app.on('ready', createWindow); + +app.on('activate', function () { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow(); + } +}); diff --git a/Games/Contra-Shooter/manifest.json b/Games/Contra-Shooter/manifest.json new file mode 100644 index 0000000000..eaa10a7bd9 --- /dev/null +++ b/Games/Contra-Shooter/manifest.json @@ -0,0 +1,27 @@ +{ + "short_name": "contra2000", + "name": "Contra 2000", + "orientation": "landscape", + "developer": { + "name": "chris", + "url": "https://github.com/chrisDaniel" + }, + "start_url": "./index.html", + "display": "fullscreen", + "icons": [{ + "src": "/icons/touch-icon-iphone-60x60.png", + "sizes": "60x60" + },{ + "src": "/icons/touch-icon-ipad-76x76.png", + "sizes": "76x76" + },{ + "src": "/icons/touch-icon-iphone-retina-120x120", + "sizes": "120x120" + },{ + "src": "/icons/touch-icon-marketplace-128x128", + "sizes": "128x128" + },{ + "src": "/icons/touch-icon-ipad-retina-152x152", + "sizes": "152x152" + }] +} diff --git a/Games/Contra-Shooter/package-lock.json b/Games/Contra-Shooter/package-lock.json new file mode 100644 index 0000000000..850a135f4b --- /dev/null +++ b/Games/Contra-Shooter/package-lock.json @@ -0,0 +1,3319 @@ +{ + "name": "contra2000", + "version": "2.1.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + } + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "applause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/applause/-/applause-1.2.2.tgz", + "integrity": "sha1-qEaFeegfZzl7tWNMKZU77c0PVsA=", + "dev": true, + "requires": { + "cson-parser": "^1.1.0", + "js-yaml": "^3.3.0", + "lodash": "^3.10.0" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + } + } + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "asar": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/asar/-/asar-0.8.3.tgz", + "integrity": "sha1-wuA/kFRRbbv1Z1noVOnOjRqdOdM=", + "dev": true, + "requires": { + "chromium-pickle-js": "0.1.0", + "commander": "2.3.0", + "cuint": "0.1.5", + "glob": "^5.0.5", + "minimatch": "2.0.4", + "mkdirp": "^0.5.0", + "mksnapshot": "0.1.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.4.tgz", + "integrity": "sha1-g76hFYA+egl6eAIkJyh+23Yvr+0=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + } + } + }, + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true, + "requires": { + "readable-stream": "~1.0.26" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=", + "dev": true + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.x.x" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browserify-zlib": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", + "dev": true, + "requires": { + "pako": "~0.2.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caseless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz", + "integrity": "sha1-t7Zc5r8UE4hlOc/VM/CzDv+pz4g=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chromium-pickle-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.1.0.tgz", + "integrity": "sha1-HUixB9ghJqLz4hHC6iX4A7pVGyE=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "coffeescript": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", + "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + } + }, + "connect-livereload": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.5.4.tgz", + "integrity": "sha1-gBV9E3HJ83zBQDmrGJWXDRGdw7w=", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.x.x" + } + }, + "cson-parser": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.3.5.tgz", + "integrity": "sha1-fsZ14DkUVTO/KmqFYHPxWZ2cLSQ=", + "dev": true, + "requires": { + "coffee-script": "^1.10.0" + }, + "dependencies": { + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "dev": true + } + } + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true + }, + "cuint": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.1.5.tgz", + "integrity": "sha1-uEixhGbz8YD5bR624HzLfs8Sai4=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decompress-zip": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.1.0.tgz", + "integrity": "sha1-vOYMEWZPLWYPykvPY0r23l1sFMc=", + "dev": true, + "requires": { + "binary": "^0.3.0", + "graceful-fs": "^3.0.0", + "mkpath": "^0.1.0", + "nopt": "^3.0.1", + "q": "^1.1.2", + "readable-stream": "^1.1.8", + "touch": "0.0.3" + }, + "dependencies": { + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "^1.1.0" + } + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", + "dev": true, + "requires": { + "async": "~0.9.0", + "combined-stream": "~0.0.4", + "mime-types": "~2.0.3" + }, + "dependencies": { + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + } + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs-extra": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.18.2.tgz", + "integrity": "sha1-rwXKcCsLbfp96AOh96tHnsXCFSU=", + "dev": true, + "requires": { + "graceful-fs": "^3.0.5", + "jsonfile": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "^1.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "github-releases": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/github-releases/-/github-releases-0.3.2.tgz", + "integrity": "sha1-lFCHdRhaXrFuimt4fGTAIlAFquI=", + "dev": true, + "requires": { + "grunt": "0.4", + "minimatch": "0.2.12", + "optimist": "~0.4.0", + "prettyjson": "^1.1.3", + "request": "~2.51.0" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + }, + "dependencies": { + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "boom": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", + "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", + "dev": true, + "requires": { + "hoek": "0.9.x" + } + }, + "caseless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.8.0.tgz", + "integrity": "sha1-W8oogdQUN/VLJAfr40iIx7mtT30=", + "dev": true + }, + "coffee-script": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "dev": true + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "cryptiles": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", + "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", + "dev": true, + "requires": { + "boom": "0.4.x" + } + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "~3.2.9", + "lodash": "~2.4.1" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "forever-agent": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz", + "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=", + "dev": true + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "hawk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", + "dev": true, + "requires": { + "boom": "0.4.x", + "cryptiles": "0.2.x", + "hoek": "0.9.x", + "sntp": "0.2.x" + } + }, + "hoek": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", + "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", + "dev": true + }, + "iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true + }, + "minimatch": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.12.tgz", + "integrity": "sha1-6oKgEqxmLH3fqhRPHBR+aUb12vs=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "oauth-sign": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.5.0.tgz", + "integrity": "sha1-12f1FpMlYg6rLgh+8MRy53PbZGE=", + "dev": true + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true + }, + "request": { + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.51.0.tgz", + "integrity": "sha1-NdALvswBLlX5B7G9ng29V3v+8m4=", + "dev": true, + "requires": { + "aws-sign2": "~0.5.0", + "bl": "~0.9.0", + "caseless": "~0.8.0", + "combined-stream": "~0.0.5", + "forever-agent": "~0.5.0", + "form-data": "~0.2.0", + "hawk": "1.1.1", + "http-signature": "~0.10.0", + "json-stringify-safe": "~5.0.0", + "mime-types": "~1.0.1", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.5.0", + "qs": "~2.3.1", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "sntp": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", + "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", + "dev": true, + "requires": { + "hoek": "0.9.x" + } + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + } + } + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "grunt": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.4.tgz", + "integrity": "sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==", + "dev": true, + "requires": { + "coffeescript": "~1.10.0", + "dateformat": "~1.0.12", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.3.0", + "glob": "~7.0.0", + "grunt-cli": "~1.2.0", + "grunt-known-options": "~1.1.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.13.0", + "minimatch": "~3.0.2", + "mkdirp": "~0.5.1", + "nopt": "~3.0.6", + "path-is-absolute": "~1.0.0", + "rimraf": "~2.6.2" + }, + "dependencies": { + "grunt-cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", + "dev": true, + "requires": { + "findup-sync": "~0.3.0", + "grunt-known-options": "~1.1.0", + "nopt": "~3.0.6", + "resolve": "~1.1.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "grunt-asar": { + "version": "github:bwin/grunt-asar#7406ecbfb16817d0af6ca1a737f74a8acc2f8c61", + "from": "github:bwin/grunt-asar#update-asar", + "dev": true, + "requires": { + "asar": "^0.8.3", + "glob": "^6.0.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "grunt-contrib-clean": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", + "integrity": "sha1-Vkq/LQN4qYOhW54/MO51tzjEBjg=", + "dev": true, + "requires": { + "async": "^1.5.2", + "rimraf": "^2.5.1" + } + }, + "grunt-contrib-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-1.0.1.tgz", + "integrity": "sha1-YVCYYwhOhx1+ht5IwBUlntl3Rb0=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "source-map": "^0.5.3" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "grunt-contrib-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-1.0.2.tgz", + "integrity": "sha1-XPkzuRpnOGBEJzwLJERgPNmIebo=", + "dev": true, + "requires": { + "async": "^1.5.2", + "connect": "^3.4.0", + "connect-livereload": "^0.5.0", + "http2": "^3.3.4", + "morgan": "^1.6.1", + "opn": "^4.0.0", + "portscanner": "^1.0.0", + "serve-index": "^1.7.1", + "serve-static": "^1.10.0" + } + }, + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "grunt-contrib-uglify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-2.3.0.tgz", + "integrity": "sha1-s9AmDr3WzvoS/y+Onh4ln33kIW8=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "maxmin": "^1.1.0", + "object.assign": "^4.0.4", + "uglify-js": "~2.8.21", + "uri-path": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "grunt-contrib-watch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", + "dev": true, + "requires": { + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + } + } + }, + "grunt-download-electron": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/grunt-download-electron/-/grunt-download-electron-2.1.4.tgz", + "integrity": "sha1-BVbwoLDs9qW21hNy85CNxz1RIcU=", + "dev": true, + "requires": { + "decompress-zip": "0.0.4", + "github-releases": "0.3.2", + "grunt": "0.4", + "progress": "1.1.2", + "wrench": "1.5.4" + }, + "dependencies": { + "argparse": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", + "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", + "dev": true, + "requires": { + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" + }, + "dependencies": { + "underscore.string": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", + "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", + "dev": true + } + } + }, + "async": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", + "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", + "dev": true + }, + "coffee-script": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", + "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", + "dev": true + }, + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "dateformat": { + "version": "1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", + "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", + "dev": true + }, + "decompress-zip": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.0.4.tgz", + "integrity": "sha1-xWJDH3aviXCvr1AibkLhxHpXsIY=", + "dev": true, + "requires": { + "binary": "~0.3.0", + "mkpath": "~0.1.0", + "nopt": "~2.1.2", + "q": "~0.9.6", + "readable-stream": "~1.1.8", + "touch": "0.0.2" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "findup-sync": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", + "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", + "dev": true, + "requires": { + "glob": "~3.2.9", + "lodash": "~2.4.1" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "grunt": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", + "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", + "dev": true, + "requires": { + "async": "~0.1.22", + "coffee-script": "~1.3.3", + "colors": "~0.6.2", + "dateformat": "1.0.2-1.2.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.1.2", + "getobject": "~0.1.0", + "glob": "~3.1.21", + "grunt-legacy-log": "~0.1.0", + "grunt-legacy-util": "~0.2.0", + "hooker": "~0.2.3", + "iconv-lite": "~0.2.11", + "js-yaml": "~2.0.5", + "lodash": "~0.9.2", + "minimatch": "~0.2.12", + "nopt": "~1.0.10", + "rimraf": "~2.2.8", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "grunt-legacy-log": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", + "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "grunt-legacy-log-utils": "~0.1.1", + "hooker": "~0.2.3", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-log-utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", + "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", + "dev": true, + "requires": { + "colors": "~0.6.2", + "lodash": "~2.4.1", + "underscore.string": "~2.3.3" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + }, + "underscore.string": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", + "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", + "dev": true + } + } + }, + "grunt-legacy-util": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", + "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", + "dev": true, + "requires": { + "async": "~0.1.22", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~0.9.2", + "underscore.string": "~2.2.1", + "which": "~1.0.5" + } + }, + "iconv-lite": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", + "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", + "dev": true + }, + "js-yaml": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", + "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", + "dev": true, + "requires": { + "argparse": "~ 0.1.11", + "esprima": "~ 1.0.2" + } + }, + "lodash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", + "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "q": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz", + "integrity": "sha1-TeLmyzspCIyeTLwDv51C+5bOL3U=", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "touch": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.2.tgz", + "integrity": "sha1-plp3d5Xly74SmUmb3EIoH/shtfQ=", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "underscore.string": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", + "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", + "dev": true + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + } + } + }, + "grunt-known-options": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", + "dev": true + }, + "grunt-legacy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", + "dev": true, + "requires": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.5" + } + }, + "grunt-legacy-log-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", + "dev": true, + "requires": { + "chalk": "~2.4.1", + "lodash": "~4.17.10" + } + }, + "grunt-legacy-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", + "dev": true, + "requires": { + "async": "~1.5.2", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" + } + }, + "grunt-processhtml": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/grunt-processhtml/-/grunt-processhtml-0.4.2.tgz", + "integrity": "sha512-kOEBUHmDKauUcg0q5FMJbhCTJajzdCEcREhnNzfCehYpKNBiOKD8CMz24lJEOKECXL+ekUux7a0/F/XgRSBcbA==", + "dev": true, + "requires": { + "async": "^1.5.2", + "htmlprocessor": "^0.2.4", + "lodash": "^4.17.5" + } + }, + "grunt-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/grunt-replace/-/grunt-replace-1.0.1.tgz", + "integrity": "sha1-kKeVMvuJBB/kJ8h9QlI4sPiGZRo=", + "dev": true, + "requires": { + "applause": "1.2.2", + "chalk": "^1.1.0", + "file-sync-cmp": "^0.1.0", + "lodash": "^4.11.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "gzip-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", + "integrity": "sha1-Zs+LEBBHInuVus5uodoMF37Vwi8=", + "dev": true, + "requires": { + "browserify-zlib": "^0.1.4", + "concat-stream": "^1.4.1" + } + }, + "har-validator": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", + "integrity": "sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI=", + "dev": true, + "requires": { + "bluebird": "^2.9.30", + "chalk": "^1.0.0", + "commander": "^2.8.1", + "is-my-json-valid": "^2.12.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "hawk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "integrity": "sha1-HnMc45RH+h0PbXB/e87r7A/R7B8=", + "dev": true, + "requires": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "htmlprocessor": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/htmlprocessor/-/htmlprocessor-0.2.6.tgz", + "integrity": "sha1-rJ9HfsU3g7jXprZ9e2w1HqXXPTU=", + "dev": true, + "requires": { + "lodash": "~2.4.1" + }, + "dependencies": { + "lodash": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", + "dev": true + } + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "dependencies": { + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "^0.1.5", + "ctype": "0.5.3" + } + }, + "http2": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/http2/-/http2-3.3.7.tgz", + "integrity": "sha512-puSi8M8WNlFJm9Pk4c/Mbz9Gwparuj3gO9/RRO5zv6piQ0FY+9Qywp0PdWshYgsMJSalixFY7eC6oPu0zRxLAQ==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "maxmin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-1.1.0.tgz", + "integrity": "sha1-cTZehKmd2Piz99X94vANHn9zvmE=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "figures": "^1.0.1", + "gzip-size": "^1.0.0", + "pretty-bytes": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, + "mime-db": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", + "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=", + "dev": true + }, + "mime-types": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", + "dev": true, + "requires": { + "mime-db": "~1.12.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mkpath": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", + "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=", + "dev": true + }, + "mksnapshot": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.1.0.tgz", + "integrity": "sha1-99CavKgGrYw3gNpwG7GHeNfOaaw=", + "dev": true, + "requires": { + "decompress-zip": "0.1.0", + "fs-extra": "0.18.2", + "request": "2.55.0" + } + }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dev": true, + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "natives": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", + "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz", + "integrity": "sha1-fb6uRPbKRU4fFoRR1jB0ZzWBPOM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "optimist": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.4.0.tgz", + "integrity": "sha1-y47Dfy/jqphky2eidSUOfhliCiU=", + "dev": true, + "requires": { + "wordwrap": "~0.0.2" + } + }, + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "portscanner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-1.2.0.tgz", + "integrity": "sha1-sUu9olfRTDEPqcwJaCrwLUCWGAI=", + "dev": true, + "requires": { + "async": "1.5.2" + } + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } + }, + "prettyjson": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.1.tgz", + "integrity": "sha1-/P+rQdGcq0365eV15kJGYZsS0ok=", + "dev": true, + "requires": { + "colors": "^1.1.2", + "minimist": "^1.2.0" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.2.tgz", + "integrity": "sha1-h/28fHanhAIIl7XpZlVUsF/FjNE=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", + "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.55.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", + "integrity": "sha1-11wc32eddrsQD5v/4f5VG1wk6T0=", + "dev": true, + "requires": { + "aws-sign2": "~0.5.0", + "bl": "~0.9.0", + "caseless": "~0.9.0", + "combined-stream": "~0.0.5", + "forever-agent": "~0.6.0", + "form-data": "~0.2.0", + "har-validator": "^1.4.0", + "hawk": "~2.3.0", + "http-signature": "~0.10.0", + "isstream": "~0.1.1", + "json-stringify-safe": "~5.0.0", + "mime-types": "~2.0.1", + "node-uuid": "~1.4.0", + "oauth-sign": "~0.6.0", + "qs": "~2.4.0", + "stringstream": "~0.0.4", + "tough-cookie": ">=0.12.0", + "tunnel-agent": "~0.4.0" + } + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.x.x" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "touch": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", + "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", + "dev": true + }, + "underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "dev": true, + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "uri-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", + "integrity": "sha1-l0fwGDWJM8Md4PzP2C0TjmcmLjI=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "wrench": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.4.tgz", + "integrity": "sha1-Lo2dPbNWjMHAAaTI3OyncuXSFkM=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + } + } + } + } +} diff --git a/Games/Contra-Shooter/package.json b/Games/Contra-Shooter/package.json new file mode 100644 index 0000000000..199efc29df --- /dev/null +++ b/Games/Contra-Shooter/package.json @@ -0,0 +1,18 @@ +{ + "name": "contra2000", + "version": "2.1.3", + "main": "main.js", + "devDependencies": { + "grunt": "^1.0.1", + "grunt-asar": "bwin/grunt-asar#update-asar", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-concat": "^1.0.1", + "grunt-contrib-connect": "^1.0.2", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-uglify": "^2.0.0", + "grunt-contrib-watch": "^1.0.0", + "grunt-download-electron": "^2.1.4", + "grunt-processhtml": "^0.4.1", + "grunt-replace": "^1.0.1" + } +} diff --git a/Games/Contra-Shooter/tasks/grunt-resources.js b/Games/Contra-Shooter/tasks/grunt-resources.js new file mode 100644 index 0000000000..8080e1f418 --- /dev/null +++ b/Games/Contra-Shooter/tasks/grunt-resources.js @@ -0,0 +1,43 @@ +module.exports = function (grunt) { + function task() { + var path = require("path"), + options = this.options({ + "varname" : "game.resources" + }); + res = [], + audio = {}; + + grunt.log.debug("options: " + JSON.stringify(options)); + grunt.log.debug("files: " + JSON.stringify(this.files)); + + this.files.forEach(function (file) { + file.src.forEach(function (src) { + var name = path.basename(src, path.extname(src)); + if ((file.type !== "audio") || (!audio.hasOwnProperty(name))) { + if (file.type === "audio") { + audio[name] = true; + } + res.push({ + "name" : name, + "type" : file.type, + "src" : ( + file.type === "audio" ? + path.dirname(src) + "/" : + src + ) + }); + } + }); + }); + + grunt.log.debug(JSON.stringify(res)); + + grunt.file.write( + options.dest, + options.varname + " = " + JSON.stringify(res, null, 4) + ";" + ); + grunt.log.ok(options.dest) + } + + grunt.registerMultiTask("resources", "Build melonJS resources.js", task); +}; diff --git a/README.md b/README.md index 02880e98e7..6290c38336 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ This repository also provides one such platforms where contributers come over an | [Maze_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Maze_Game) | [TriHand_Tactics](https://github.com/kunjgit/GameZone/tree/main/Games/TriHand_Tactics) | [Earth_Guardian](https://github.com/kunjgit/GameZone/tree/main/Games/Earth_Guardian) | [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) | | [Ball_Shooting_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Ball_Shooting_Game) | [CatchTheBall](https://github.com/kunjgit/GameZone/tree/main/Games/CatchTheBall) | +| [Contra-Shooter](https://github.com/VesperAkshay/GameZone/tree/main/Games/Contra-Shooter)| | [Memory_Cards_Game](https://github.com/kunjgit/GameZone/tree/main/Games/Memory_Cards_Game) | @@ -208,6 +209,7 @@ This repository also provides one such platforms where contributers come over an | [Rock_paper_scissor](https://github.com/kunjgit/GameZone/tree/main/Games/Rock_paper_scissor) | +
diff --git a/assets/images/Contra-Shooter.png b/assets/images/Contra-Shooter.png new file mode 100644 index 0000000000..dde2203b9c Binary files /dev/null and b/assets/images/Contra-Shooter.png differ diff --git a/assets/images/PressStart2P.png b/assets/images/PressStart2P.png new file mode 100644 index 0000000000..73766690aa Binary files /dev/null and b/assets/images/PressStart2P.png differ diff --git a/assets/images/bad-guys.png b/assets/images/bad-guys.png new file mode 100644 index 0000000000..bad714b4bc Binary files /dev/null and b/assets/images/bad-guys.png differ diff --git a/assets/images/bill-lance.png b/assets/images/bill-lance.png new file mode 100644 index 0000000000..7e66088d5e Binary files /dev/null and b/assets/images/bill-lance.png differ diff --git a/assets/images/contra-tile-map.png b/assets/images/contra-tile-map.png new file mode 100644 index 0000000000..6182a13966 Binary files /dev/null and b/assets/images/contra-tile-map.png differ diff --git a/assets/images/explosion-sprite.png b/assets/images/explosion-sprite.png new file mode 100644 index 0000000000..118beb66f1 Binary files /dev/null and b/assets/images/explosion-sprite.png differ diff --git a/assets/images/icon_badge_24_48.png b/assets/images/icon_badge_24_48.png new file mode 100644 index 0000000000..871702a3bd Binary files /dev/null and b/assets/images/icon_badge_24_48.png differ diff --git a/assets/images/icon_select_30_25.png b/assets/images/icon_select_30_25.png new file mode 100644 index 0000000000..597987de88 Binary files /dev/null and b/assets/images/icon_select_30_25.png differ diff --git a/assets/images/img_captured.png b/assets/images/img_captured.png new file mode 100644 index 0000000000..629990969d Binary files /dev/null and b/assets/images/img_captured.png differ diff --git a/assets/images/img_neslogo.png b/assets/images/img_neslogo.png new file mode 100644 index 0000000000..914c130fb9 Binary files /dev/null and b/assets/images/img_neslogo.png differ diff --git a/assets/images/img_splashpage.png b/assets/images/img_splashpage.png new file mode 100644 index 0000000000..6b110ca5a2 Binary files /dev/null and b/assets/images/img_splashpage.png differ diff --git a/assets/images/power-ups-sprite.png b/assets/images/power-ups-sprite.png new file mode 100644 index 0000000000..fd417d8bae Binary files /dev/null and b/assets/images/power-ups-sprite.png differ diff --git a/assets/images/rock_platform-sprite.png b/assets/images/rock_platform-sprite.png new file mode 100644 index 0000000000..c66433d45f Binary files /dev/null and b/assets/images/rock_platform-sprite.png differ diff --git a/assets/images/touch-icon-ipad-76x76.png b/assets/images/touch-icon-ipad-76x76.png new file mode 100644 index 0000000000..142f22f241 Binary files /dev/null and b/assets/images/touch-icon-ipad-76x76.png differ diff --git a/assets/images/touch-icon-ipad-retina-152x152.png b/assets/images/touch-icon-ipad-retina-152x152.png new file mode 100644 index 0000000000..960fae6c33 Binary files /dev/null and b/assets/images/touch-icon-ipad-retina-152x152.png differ diff --git a/assets/images/touch-icon-iphone-60x60.png b/assets/images/touch-icon-iphone-60x60.png new file mode 100644 index 0000000000..9453e148ed Binary files /dev/null and b/assets/images/touch-icon-iphone-60x60.png differ diff --git a/assets/images/touch-icon-iphone-retina-120x120.png b/assets/images/touch-icon-iphone-retina-120x120.png new file mode 100644 index 0000000000..9c27b0856c Binary files /dev/null and b/assets/images/touch-icon-iphone-retina-120x120.png differ diff --git a/assets/images/touch-icon-marketplace-128x128.png b/assets/images/touch-icon-marketplace-128x128.png new file mode 100644 index 0000000000..e6796420d7 Binary files /dev/null and b/assets/images/touch-icon-marketplace-128x128.png differ diff --git a/assets/js/gamesData.json b/assets/js/gamesData.json index 10de2b4aa3..0e3d416feb 100644 --- a/assets/js/gamesData.json +++ b/assets/js/gamesData.json @@ -1949,10 +1949,15 @@ "gameUrl": "Earth_Guardian", "thumbnailUrl": "Earth_Guardian.png" }, - "387":{ + "388":{ "gameTitle": "Memory Cards Game", "gameUrl": "Memory_Cards_Game", "thumbnailUrl": "Memory_Cards_Game.png" + }, + "389":{ + "gameTitle": "Contra-Shooter", + "gameUrl": "Contra-Shooter", + "thumbnailUrl": "Contra-Shooter.png" }