commit 86548b6057718bc9abc1ccc63fd82bd447c0a160 Author: Renge Date: Mon May 23 06:00:37 2022 -0400 init repo diff --git a/hw1/README.md b/hw1/README.md new file mode 100644 index 0000000..b303e50 --- /dev/null +++ b/hw1/README.md @@ -0,0 +1,17 @@ +# CSE380_HW1 +--- +## GAME concentration_multi(temp) +### prepare state +all lights are off +single click anyone to set the number of players +double click to start the game +### play state +at each player's tern, single click to select a single blink +nothing happened -- earn 1 pt +turn white for a second -- earn no pt +### end state +all lights turn white to validate +then all blink will turn to the color of the player who chose that blink +double click to reset to prepare state +## GAME 2 +## GAME 3 diff --git a/hw1/concentration_multi.cpp b/hw1/concentration_multi.cpp new file mode 100644 index 0000000..90fd71d --- /dev/null +++ b/hw1/concentration_multi.cpp @@ -0,0 +1,224 @@ +enum signalStates { PREPARE, INERT, GO, RESOLVE, CHECK, END }; +byte signalState = PREPARE; +enum player {PLAYER1, PLAYER2, PLAYER3, PLAYER4, PLAYER5, PLAYER6};//these modes will simply be different colors +byte player = PLAYER1;//the default mode when the game begins +byte player_num = 0; +byte chosen_player = 6; +Timer warn_timer; +Timer check_timer; +void loop() { + switch (signalState) { + case PREPARE: + prepareLoop(); + break; + case INERT: + case GO: + case RESOLVE: + playLoop(); + break; + case CHECK: + checkLoop(); + break; + case END: + endLoop(); + break; + } +} + +void prepareLoop() { + FOREACH_FACE(f) { + if (getSignalState(getLastValueReceivedOnFace(f)) == INERT) { + signalState = INERT; + player = getPlayer(getLastValueReceivedOnFace(f)); + player_num = player + 1; + byte sendData = (signalState << 3) + (player); + setValueSentOnAllFaces(sendData); + } + else { + if (player_num == 0) { + setColor(OFF); + } + else { + for ( byte i = 0; i < player_num; i ++) { + setColorOnFace(getColor(i+1), i); + } + } + if (buttonSingleClicked()) { + player_num = (player_num + 1) % 7; + } + if (buttonDoubleClicked() && player_num != 0) { + player = player_num - 1; + signalState = INERT; + byte sendData = (signalState << 3) + (player); + setValueSentOnAllFaces(sendData); + } + else { + setValueSentOnAllFaces(0); + } + } + } +} + +void playLoop() { + switch (signalState) { + case INERT: + inertLoop(); + break; + case GO: + goLoop(); + break; + case RESOLVE: + resolveLoop(); + break; + } + displaySignalState(); + byte sendData = (signalState << 3) + (player); + setValueSentOnAllFaces(sendData); +} + +void inertLoop() { + //set myself to GO + if (buttonSingleClicked()) { + if (chosen_player == 6) { + chosen_player = player; + } + else { + warn_timer.set(1000); + } + signalState = GO; + player = (player + 1) % (player_num);//adds one to game mode, but 3+1 becomes 0 + } + //listen for neighbors in GO + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + if (getSignalState(getLastValueReceivedOnFace(f)) == GO) { //a neighbor saying GO! + signalState = GO; + player = getPlayer(getLastValueReceivedOnFace(f)); + } + } + } +} +void goLoop() { + signalState = RESOLVE; //I default to this at the start of the loop. Only if I see a problem does this not happen + //look for neighbors who have not heard the GO news + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + if (getSignalState(getLastValueReceivedOnFace(f)) == INERT) {//This neighbor doesn't know it's GO time. Stay in GO + signalState = GO; + } + } + } +} + +void resolveLoop() { + if (chosen_player == 6) { + signalState = INERT; //I default to this at the start of the loop. Only if I see a problem does this not happen + } + else { + check_timer.set(2000); + signalState = CHECK; + } + //look for neighbors who have not moved to RESOLVE + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + if (getSignalState(getLastValueReceivedOnFace(f)) == GO) {//This neighbor isn't in RESOLVE. Stay in RESOLVE + signalState = RESOLVE; + } + } + } +} + +void checkLoop() { + if (check_timer.isExpired()) { + setColor(OFF); + signalState = END; + } + else { + setColor(WHITE); + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + if (getSignalState(getLastValueReceivedOnFace(f)) == INERT) {//This neighbor isn't in RESOLVE. Stay in RESOLVE + signalState = INERT; + } + } + } + setValueSentOnAllFaces(6 << 3); + } +} + +void endLoop() { + setColor(getColor(getRealPlayer(chosen_player))) + FOREACH_FACE(f) { + if (!isValueReceivedOnFaceExpired(f)) { //a neighbor! + if (getSignalState(getLastValueReceivedOnFace(f)) == PREPARE) {//This neighbor isn't in RESOLVE. Stay in RESOLVE + signalState = PREPARE; + player = PLAYER1; + player_num = 0; + chosen_player = 6; + setColor(OFF); + } + } + } + if (buttonDoubleClicked()) { + signalState = PREPARE; + player = PLAYER1; + player_num = 0; + chosen_player = 6; + setValueSentOnAllFaces(0); + } +} + +void displaySignalState() { + if (warn_timer.isExpired()) { + switch (signalState) { + case INERT: + setColor(getColor(getRealPlayer(player))); + break; + case GO: + case RESOLVE: + setColor(WHITE); + break; + } + } + else { + setColor(WHITE); + } +} + +Color getColor(data) { + switch (data) { + case 0: + return (makeColorRGB(0,0,0)); + break; + case 1: + return (makeColorRGB(255,0,0)); + break; + case 2: + return (makeColorRGB(0,255,0)); + break; + case 3: + return (makeColorRGB(0,0,255)); + break; + case 4: + return (makeColorRGB(255,0,255)); + break; + case 5: + return (makeColorRGB(255,255,0)); + break; + case 6: + return (makeColorRGB(0,255,255)); + break; + } +} + +byte getRealPlayer(byte data) { + return (data + 1) % player_num + 1; +} + +byte getPlayer(byte data) { + return (data & 7);//returns bits E and F +} + +byte getSignalState(byte data) { + return ((data >> 3) & 7);//returns bits C and D +} \ No newline at end of file diff --git a/hw3/.gitignore b/hw3/.gitignore new file mode 100644 index 0000000..086ddd8 --- /dev/null +++ b/hw3/.gitignore @@ -0,0 +1,18 @@ +# Exclude node modules +node_modules + +# Exclude the compiled project +dist/* + +# Include the demo_assets folder +!dist/demo_assets/ + +# Include the built-in asset folder +!dist/builtin/ + +# Include the hw1 assets +!dist/hw3_assets/ + +### IF YOU ARE MAKING A PROJECT, YOU MAY WANT TO UNCOMMENT THIS LINE ### +# !dist/assets/ + diff --git a/hw3/dist/builtin/shaders/label.fshader b/hw3/dist/builtin/shaders/label.fshader new file mode 100644 index 0000000..a248cba --- /dev/null +++ b/hw3/dist/builtin/shaders/label.fshader @@ -0,0 +1,77 @@ +precision mediump float; + +uniform vec4 u_BackgroundColor; +uniform vec4 u_BorderColor; +uniform float u_BorderWidth; +uniform float u_BorderRadius; +uniform vec2 u_MaxSize; + +varying vec4 v_Position; + +void main(){ + vec2 adj_MaxSize = u_MaxSize - u_BorderWidth; + vec2 rad_MaxSize = u_MaxSize - u_BorderRadius; + vec2 rad2_MaxSize = u_MaxSize - 2.0*u_BorderRadius; + + bool inX = (v_Position.x < adj_MaxSize.x) && (v_Position.x > -adj_MaxSize.x); + bool inY = (v_Position.y < adj_MaxSize.y) && (v_Position.y > -adj_MaxSize.y); + + bool inRadiusRangeX = (v_Position.x < rad_MaxSize.x) && (v_Position.x > -rad_MaxSize.x); + bool inRadiusRangeY = (v_Position.y < rad_MaxSize.y) && (v_Position.y > -rad_MaxSize.y); + + bool inRadius2RangeX = (v_Position.x < rad2_MaxSize.x) && (v_Position.x > -rad2_MaxSize.x); + bool inRadius2RangeY = (v_Position.y < rad2_MaxSize.y) && (v_Position.y > -rad2_MaxSize.y); + + if(inX && inY){ + // Inside bounds, draw background color + gl_FragColor = u_BackgroundColor; + } else { + // In boundary, draw border color + gl_FragColor = u_BorderColor; + } + + // This isn't working well right now + /* + if(inRadius2RangeX || inRadius2RangeY){ + // Draw normally + if(inX && inY){ + // Inside bounds, draw background color + gl_FragColor = u_BackgroundColor; + } else { + // In boundary, draw border color + gl_FragColor = u_BorderColor; + } + } else if(inRadiusRangeX || inRadiusRangeY){ + // Draw a rounded boundary for the inner part + float x = v_Position.x - sign(v_Position.x)*rad2_MaxSize.x; + float y = v_Position.y - sign(v_Position.y)*rad2_MaxSize.y; + + float radSq = x*x + y*y; + float bRadSq = u_BorderRadius*u_BorderRadius; + + if(radSq > bRadSq){ + // Outside of radius - draw as transparent + gl_FragColor = u_BorderColor; + } else { + gl_FragColor = u_BackgroundColor; + } + } else { + // Both coordinates are in the circular section + float x = v_Position.x - sign(v_Position.x)*rad_MaxSize.x; + float y = v_Position.y - sign(v_Position.y)*rad_MaxSize.y; + + float radSq = x*x + y*y; + float bRadSq = u_BorderRadius*u_BorderRadius; + + if(radSq > bRadSq){ + // Outside of radius - draw as transparent + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else if(sqrt(bRadSq) - sqrt(radSq) < u_BorderWidth) { + // In border + gl_FragColor = u_BorderColor; + } else { + gl_FragColor = u_BackgroundColor; + } + } + */ +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/label.vshader b/hw3/dist/builtin/shaders/label.vshader new file mode 100644 index 0000000..093de28 --- /dev/null +++ b/hw3/dist/builtin/shaders/label.vshader @@ -0,0 +1,12 @@ +attribute vec4 a_Position; + +uniform mat4 u_Transform; + +varying vec4 v_Position; + +void main(){ + gl_Position = u_Transform * a_Position; + + // Pass position to the fragment shader + v_Position = a_Position; +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/point.fshader b/hw3/dist/builtin/shaders/point.fshader new file mode 100644 index 0000000..08dd687 --- /dev/null +++ b/hw3/dist/builtin/shaders/point.fshader @@ -0,0 +1,7 @@ +precision mediump float; + +uniform vec4 u_Color; + +void main(){ + gl_FragColor = u_Color; +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/point.vshader b/hw3/dist/builtin/shaders/point.vshader new file mode 100644 index 0000000..bd147d4 --- /dev/null +++ b/hw3/dist/builtin/shaders/point.vshader @@ -0,0 +1,8 @@ +attribute vec4 a_Position; + +uniform float u_PointSize; + +void main(){ + gl_Position = a_Position; + gl_PointSize = u_PointSize; +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/rect.fshader b/hw3/dist/builtin/shaders/rect.fshader new file mode 100644 index 0000000..08dd687 --- /dev/null +++ b/hw3/dist/builtin/shaders/rect.fshader @@ -0,0 +1,7 @@ +precision mediump float; + +uniform vec4 u_Color; + +void main(){ + gl_FragColor = u_Color; +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/rect.vshader b/hw3/dist/builtin/shaders/rect.vshader new file mode 100644 index 0000000..24c7378 --- /dev/null +++ b/hw3/dist/builtin/shaders/rect.vshader @@ -0,0 +1,7 @@ +attribute vec4 a_Position; + +uniform mat4 u_Transform; + +void main(){ + gl_Position = u_Transform * a_Position; +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/sprite.fshader b/hw3/dist/builtin/shaders/sprite.fshader new file mode 100644 index 0000000..a4fb440 --- /dev/null +++ b/hw3/dist/builtin/shaders/sprite.fshader @@ -0,0 +1,9 @@ +precision mediump float; + +uniform sampler2D u_Sampler; + +varying vec2 v_TexCoord; + +void main(){ + gl_FragColor = texture2D(u_Sampler, v_TexCoord); +} \ No newline at end of file diff --git a/hw3/dist/builtin/shaders/sprite.vshader b/hw3/dist/builtin/shaders/sprite.vshader new file mode 100644 index 0000000..f04fc3c --- /dev/null +++ b/hw3/dist/builtin/shaders/sprite.vshader @@ -0,0 +1,13 @@ +attribute vec4 a_Position; +attribute vec2 a_TexCoord; + +uniform mat4 u_Transform; +uniform vec2 u_texShift; +uniform vec2 u_texScale; + +varying vec2 v_TexCoord; + +void main(){ + gl_Position = u_Transform * a_Position; + v_TexCoord = a_TexCoord*u_texScale + u_texShift; +} \ No newline at end of file diff --git a/hw3/dist/demo_assets/images/platformer_background.png b/hw3/dist/demo_assets/images/platformer_background.png new file mode 100644 index 0000000..2f221a9 Binary files /dev/null and b/hw3/dist/demo_assets/images/platformer_background.png differ diff --git a/hw3/dist/demo_assets/images/wolfie2d_text.png b/hw3/dist/demo_assets/images/wolfie2d_text.png new file mode 100644 index 0000000..53d51d9 Binary files /dev/null and b/hw3/dist/demo_assets/images/wolfie2d_text.png differ diff --git a/hw3/dist/demo_assets/sounds/jump.wav b/hw3/dist/demo_assets/sounds/jump.wav new file mode 100644 index 0000000..71d9bb8 Binary files /dev/null and b/hw3/dist/demo_assets/sounds/jump.wav differ diff --git a/hw3/dist/demo_assets/sounds/title.mp3 b/hw3/dist/demo_assets/sounds/title.mp3 new file mode 100644 index 0000000..27d66fc Binary files /dev/null and b/hw3/dist/demo_assets/sounds/title.mp3 differ diff --git a/hw3/dist/demo_assets/spritesheets/platformer/player.json b/hw3/dist/demo_assets/spritesheets/platformer/player.json new file mode 100644 index 0000000..c9eab64 --- /dev/null +++ b/hw3/dist/demo_assets/spritesheets/platformer/player.json @@ -0,0 +1,27 @@ +{ + "name": "PlatformerPlayer", + "spriteSheetImage": "player.png", + "spriteWidth": 16, + "spriteHeight": 16, + "columns": 5, + "rows": 1, + "durationType": "time", + "animations": [ + { + "name": "IDLE", + "frames": [ {"index": 0, "duration": 1} ] + }, + { + "name": "WALK", + "frames": [ {"index": 0, "duration": 16}, {"index": 1, "duration": 16}, {"index": 2, "duration": 16}, {"index": 3, "duration": 16} ] + }, + { + "name": "JUMP", + "frames":[ {"index": 4, "duration": 32}] + }, + { + "name": "FALL", + "frames":[ {"index": 4, "duration": 32}] + } + ] +} \ No newline at end of file diff --git a/hw3/dist/demo_assets/spritesheets/platformer/player.png b/hw3/dist/demo_assets/spritesheets/platformer/player.png new file mode 100644 index 0000000..4db71ac Binary files /dev/null and b/hw3/dist/demo_assets/spritesheets/platformer/player.png differ diff --git a/hw3/dist/demo_assets/tilemaps/platformer/platformer.json b/hw3/dist/demo_assets/tilemaps/platformer/platformer.json new file mode 100644 index 0000000..15654e1 --- /dev/null +++ b/hw3/dist/demo_assets/tilemaps/platformer/platformer.json @@ -0,0 +1,453 @@ +{ "compressionlevel":-1, + "editorsettings": + { + "export": + { + "format":"json", + "target":"platformer.json" + } + }, + "height":20, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 0, 44, 44, 44, 44, 44, 44, 0, 44, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 36, 0, 36, 36, 36, 36, 36, 36, 36, 36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 54, 55, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 45, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 44, 62, 63, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":20, + "id":2, + "name":"Background", + "opacity":1, + "properties":[ + { + "name":"Collidable", + "type":"bool", + "value":false + }, + { + "name":"Depth", + "type":"int", + "value":0 + }], + "type":"tilelayer", + "visible":true, + "width":64, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 20, 20, 20, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 11, 12, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 16, 12, 12, 12, 12, 10, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 7, 20, 20, 20, 20, 20, 20, 8, 12, 12, 12, 12, 10, 12, 12, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 16, 12, 12, 13, 0, 0, 0, 0, 0, 0, 11, 12, 12, 12, 12, 12, 12, 9, 12, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 19, 20, 20, 20, 20, 13, 0, 0, 0, 0, 0, 0, 11, 20, 20, 20, 20, 20, 20, 20, 8, 12, 9, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 2, 2, 2, 2, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 10, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 11, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 2, 2, 2, 2, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 12, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 10, 12, 12, 12, 12, 12, 12, 10, 15, 4, 4, 4, 4, 4, 4, 4, 4], + "height":20, + "id":1, + "name":"Main", + "opacity":1, + "properties":[ + { + "name":"Collidable", + "type":"bool", + "value":true + }, + { + "name":"Depth", + "type":"int", + "value":1 + }], + "type":"tilelayer", + "visible":true, + "width":64, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":4, + "name":"Coins", + "objects":[ + { + "gid":25, + "height":16, + "id":2, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":256, + "y":272 + }, + { + "gid":25, + "height":16, + "id":3, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":272, + "y":272 + }, + { + "gid":25, + "height":16, + "id":4, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":368, + "y":288 + }, + { + "gid":25, + "height":16, + "id":5, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":384, + "y":288 + }, + { + "gid":25, + "height":16, + "id":6, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":400, + "y":288 + }, + { + "gid":25, + "height":16, + "id":7, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":688, + "y":272 + }, + { + "gid":25, + "height":16, + "id":8, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":688, + "y":288 + }, + { + "gid":25, + "height":16, + "id":9, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":688, + "y":304 + }, + { + "gid":25, + "height":16, + "id":10, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":784, + "y":256 + }, + { + "gid":25, + "height":16, + "id":11, + "name":"", + "properties":[ + { + "name":"Group", + "type":"string", + "value":"Coins" + }, + { + "name":"HasPhysics", + "type":"bool", + "value":true + }, + { + "name":"IsCollidable", + "type":"bool", + "value":false + }, + { + "name":"IsTrigger", + "type":"bool", + "value":true + }], + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":832, + "y":256 + }], + "opacity":1, + "properties":[ + { + "name":"Depth", + "type":"int", + "value":1 + }], + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":20, + "id":3, + "name":"Foreground", + "opacity":1, + "properties":[ + { + "name":"Collidable", + "type":"bool", + "value":false + }, + { + "name":"Depth", + "type":"int", + "value":2 + }], + "type":"tilelayer", + "visible":true, + "width":64, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":14, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.3.4", + "tileheight":16, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"platformer.png", + "imageheight":128, + "imagewidth":128, + "margin":0, + "name":"platformer_tileset", + "spacing":0, + "tilecount":64, + "tileheight":16, + "tilewidth":16 + }], + "tilewidth":16, + "type":"map", + "version":1.2, + "width":64 +} \ No newline at end of file diff --git a/hw3/dist/demo_assets/tilemaps/platformer/platformer.png b/hw3/dist/demo_assets/tilemaps/platformer/platformer.png new file mode 100644 index 0000000..288791f Binary files /dev/null and b/hw3/dist/demo_assets/tilemaps/platformer/platformer.png differ diff --git a/hw3/dist/hw3_assets/shaders/gradient_circle.fshader b/hw3/dist/hw3_assets/shaders/gradient_circle.fshader new file mode 100644 index 0000000..e33a5e2 --- /dev/null +++ b/hw3/dist/hw3_assets/shaders/gradient_circle.fshader @@ -0,0 +1,25 @@ +precision mediump float; + +varying vec4 v_Position; + +uniform vec4 circle_Color; + +void main(){ + // Default alpha is 0 + float alpha = 0.0; + + // Radius is 0.5, since the diameter of our quad is 1 + float radius = 0.5; + + // Get the distance squared of from (0, 0) + float dist_sq = v_Position.x*v_Position.x + v_Position.y*v_Position.y; + + if(dist_sq < radius*radius){ + // Multiply by 4, since distance squared is at most 0.25 + alpha = 4.0*dist_sq; + } + + // Use the alpha value in our color + gl_FragColor = vec4(circle_Color); + gl_FragColor.a = alpha; +} \ No newline at end of file diff --git a/hw3/dist/hw3_assets/shaders/gradient_circle.vshader b/hw3/dist/hw3_assets/shaders/gradient_circle.vshader new file mode 100644 index 0000000..9cb98c8 --- /dev/null +++ b/hw3/dist/hw3_assets/shaders/gradient_circle.vshader @@ -0,0 +1,11 @@ +attribute vec4 a_Position; + +uniform mat4 u_Transform; + +varying vec4 v_Position; + +void main(){ + gl_Position = u_Transform * a_Position; + + v_Position = a_Position; +} \ No newline at end of file diff --git a/hw3/dist/hw3_assets/shaders/linear_gradient_circle.fshader b/hw3/dist/hw3_assets/shaders/linear_gradient_circle.fshader new file mode 100644 index 0000000..616fd02 --- /dev/null +++ b/hw3/dist/hw3_assets/shaders/linear_gradient_circle.fshader @@ -0,0 +1,33 @@ +precision mediump float; + +varying vec4 v_Position; + +uniform vec4 circle_Color; +uniform vec4 default_Color; + +// HOMEWORK 3 +/* + The fragment shader is where pixel colors are decided. + You'll have to modify this code to make the circle vary between 2 colors. + Currently this will render the exact same thing as the gradient_circle shaders +*/ +void main(){ + // Default alpha is 0 + float alpha = 0.0; + + // Radius is 0.5, since the diameter of our quad is 1 + float radius = 0.5; + float ratio = (1.0-(v_Position.x+0.25+v_Position.y+0.25)); + + // Get the distance squared of from (0, 0) + float dist_sq = v_Position.x*v_Position.x + v_Position.y*v_Position.y; + + if(dist_sq < radius*radius){ + // Multiply by 4, since distance squared is at most 0.25 + alpha = 1.0; + } + + // Use the alpha value in our color + gl_FragColor = vec4(default_Color[0]*ratio+circle_Color[0]*(1.0-ratio), default_Color[1]*ratio+circle_Color[1]*(1.0-ratio), default_Color[2]*ratio+circle_Color[2]*(1.0-ratio), default_Color[3]*ratio+circle_Color[3]*(1.0-ratio)); + gl_FragColor.a = alpha; +} \ No newline at end of file diff --git a/hw3/dist/hw3_assets/shaders/linear_gradient_circle.vshader b/hw3/dist/hw3_assets/shaders/linear_gradient_circle.vshader new file mode 100644 index 0000000..9cb98c8 --- /dev/null +++ b/hw3/dist/hw3_assets/shaders/linear_gradient_circle.vshader @@ -0,0 +1,11 @@ +attribute vec4 a_Position; + +uniform mat4 u_Transform; + +varying vec4 v_Position; + +void main(){ + gl_Position = u_Transform * a_Position; + + v_Position = a_Position; +} \ No newline at end of file diff --git a/hw3/dist/hw3_assets/sprites/road.jpg b/hw3/dist/hw3_assets/sprites/road.jpg new file mode 100644 index 0000000..74d660e Binary files /dev/null and b/hw3/dist/hw3_assets/sprites/road.jpg differ diff --git a/hw3/dist/hw3_assets/sprites/stone.png b/hw3/dist/hw3_assets/sprites/stone.png new file mode 100644 index 0000000..a1f7a32 Binary files /dev/null and b/hw3/dist/hw3_assets/sprites/stone.png differ diff --git a/hw3/dist/hw3_assets/spritesheets/car.json b/hw3/dist/hw3_assets/spritesheets/car.json new file mode 100644 index 0000000..e50dc3f --- /dev/null +++ b/hw3/dist/hw3_assets/spritesheets/car.json @@ -0,0 +1,139 @@ +{ + "name": "cars", + "spriteSheetImage": "cars.png", + "spriteWidth": 256, + "spriteHeight": 256, + "leftBuffer": 0, + "rightBuffer": 0, + "topBuffer": 0, + "bottomBuffer": 0, + "columns": 3, + "rows": 4, + "animations": [ + { + "name": "driving", + "repeat": true, + "frames": [ + { + "index": 0, + "duration": 10 + }, + { + "index": 1, + "duration": 10 + } + ] + }, + { + "name": "firing", + "repeat": true, + "frames": [ + { + "index": 0, + "duration": 10 + }, + { + "index": 2, + "duration": 10 + }, + { + "index": 3, + "duration": 10 + } + ] + }, + { + "name": "damage", + "repeat": true, + "frames": [ + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, { + "index": 5, + "duration": 6 + } + ] + }, + { + "name": "dying", + "repeat": false, + "next": "dead", + "frames": [ + { + "index": 6, + "duration": 10 + }, + { + "index": 7, + "duration": 10 + }, + { + "index": 8, + "duration": 10 + }, + { + "index": 9, + "duration": 10 + }, + { + "index": 10, + "duration": 10 + }, + { + "index": 11, + "duration": 10 + } + ] + }, + { + "name": "dead", + "repeat": true, + "frames": [ + { + "index": 11, + "duration": 20 + } + ] + } + ] +} diff --git a/hw3/dist/hw3_assets/spritesheets/cars.json b/hw3/dist/hw3_assets/spritesheets/cars.json new file mode 100644 index 0000000..e50dc3f --- /dev/null +++ b/hw3/dist/hw3_assets/spritesheets/cars.json @@ -0,0 +1,139 @@ +{ + "name": "cars", + "spriteSheetImage": "cars.png", + "spriteWidth": 256, + "spriteHeight": 256, + "leftBuffer": 0, + "rightBuffer": 0, + "topBuffer": 0, + "bottomBuffer": 0, + "columns": 3, + "rows": 4, + "animations": [ + { + "name": "driving", + "repeat": true, + "frames": [ + { + "index": 0, + "duration": 10 + }, + { + "index": 1, + "duration": 10 + } + ] + }, + { + "name": "firing", + "repeat": true, + "frames": [ + { + "index": 0, + "duration": 10 + }, + { + "index": 2, + "duration": 10 + }, + { + "index": 3, + "duration": 10 + } + ] + }, + { + "name": "damage", + "repeat": true, + "frames": [ + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, + { + "index": 5, + "duration": 6 + }, + { + "index": 4, + "duration": 6 + }, { + "index": 5, + "duration": 6 + } + ] + }, + { + "name": "dying", + "repeat": false, + "next": "dead", + "frames": [ + { + "index": 6, + "duration": 10 + }, + { + "index": 7, + "duration": 10 + }, + { + "index": 8, + "duration": 10 + }, + { + "index": 9, + "duration": 10 + }, + { + "index": 10, + "duration": 10 + }, + { + "index": 11, + "duration": 10 + } + ] + }, + { + "name": "dead", + "repeat": true, + "frames": [ + { + "index": 11, + "duration": 20 + } + ] + } + ] +} diff --git a/hw3/dist/hw3_assets/spritesheets/cars.png b/hw3/dist/hw3_assets/spritesheets/cars.png new file mode 100644 index 0000000..9d23917 Binary files /dev/null and b/hw3/dist/hw3_assets/spritesheets/cars.png differ diff --git a/hw3/gulpfile.js b/hw3/gulpfile.js new file mode 100644 index 0000000..c64e10f --- /dev/null +++ b/hw3/gulpfile.js @@ -0,0 +1,34 @@ +var gulp = require('gulp'); +var browserify = require('browserify'); +var source = require('vinyl-source-stream'); +var watchify = require('watchify'); +var tsify = require('tsify'); +var fancy_log = require('fancy-log'); +var paths = { + pages: ['src/*.html'] +}; + +var watchedBrowserify = watchify(browserify({ + basedir: '.', + debug: true, + entries: ['src/main.ts'], + cache: {}, + packageCache: {} +}).plugin(tsify)); + +gulp.task('copy-html', function () { + return gulp.src(paths.pages) + .pipe(gulp.dest('dist')); +}); + +function bundle() { + return watchedBrowserify + .bundle() + .on('error', fancy_log) + .pipe(source('bundle.js')) + .pipe(gulp.dest('dist')); +} + +gulp.task('default', gulp.series(gulp.parallel('copy-html'), bundle)); +watchedBrowserify.on('update', bundle); +watchedBrowserify.on('log', fancy_log); \ No newline at end of file diff --git a/hw3/package-lock.json b/hw3/package-lock.json new file mode 100644 index 0000000..ba119b8 --- /dev/null +++ b/hw3/package-lock.json @@ -0,0 +1,9236 @@ +{ + "name": "wolfie2d", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "wolfie2d", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "browserify": "^16.5.1", + "fancy-log": "^1.3.3", + "gulp": "^4.0.0", + "gulp-typescript": "^6.0.0-alpha.1", + "tsify": "^5.0.0", + "typescript": "^3.9.7", + "vinyl-source-stream": "^2.0.0", + "watchify": "^3.11.1" + } + }, + "node_modules/acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "dependencies": { + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "JSONStream": "^1.0.3", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + }, + "bin": { + "browser-pack": "bin/cmd.js" + } + }, + "node_modules/browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "dependencies": { + "resolve": "1.1.7" + } + }, + "node_modules/browser-resolve/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/browserify": { + "version": "16.5.1", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.1.tgz", + "integrity": "sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A==", + "dev": true, + "dependencies": { + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "JSONStream": "^1.0.3", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.0.0", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "browserify": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/browserify-sign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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 + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "dev": true + }, + "node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "dev": true, + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + } + }, + "node_modules/combine-source-map/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/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, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "dependencies": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "node_modules/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 + }, + "node_modules/create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/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, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "node_modules/deps-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", + "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", + "dev": true, + "dependencies": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + }, + "bin": { + "deps-sort": "bin/cmd.js" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "dependencies": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "dependencies": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/gulp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", + "integrity": "sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y=", + "dev": true, + "dependencies": { + "glob-watcher": "^5.0.0", + "gulp-cli": "^2.0.0", + "undertaker": "^1.0.0", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-typescript": { + "version": "6.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", + "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0", + "vinyl-fs": "^3.0.3" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "typescript": "~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.7.0-dev " + } + }, + "node_modules/gulp-typescript/node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-typescript/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/gulp-typescript/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp/node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "node_modules/htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true, + "dependencies": { + "source-map": "~0.5.3" + } + }, + "node_modules/insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + }, + "bin": { + "insert-module-globals": "bin/cmd.js" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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 + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "dependencies": { + "jsonify": "~0.0.0" + } + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/module-deps": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", + "dev": true, + "dependencies": { + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "module-deps": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/module-deps/node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node_modules/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, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/outpipe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true, + "dependencies": { + "shell-quote": "^1.4.2" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "dependencies": { + "path-platform": "~0.11.15" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "dependencies": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/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, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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 + }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/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, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "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" + } + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/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 + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "dependencies": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "node_modules/shasum-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", + "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", + "dev": true, + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-http": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", + "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "dependencies": { + "minimist": "^1.1.0" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "dependencies": { + "acorn-node": "^1.2.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "dependencies": { + "process": "~0.11.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/tsconfig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha1-X0J45wGACWeo/Dg/0ZZIh48qbjo=", + "dev": true, + "dependencies": { + "any-promise": "^1.3.0", + "parse-json": "^2.2.0", + "strip-bom": "^2.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tsify/-/tsify-5.0.0.tgz", + "integrity": "sha512-Dt8RL7mSz39PMOIAthxQfM0OpqmasXyWOrI0U260OF+tdz7CJV1jhP2X80aXmMP/8Y2928rRq9rESXY0xzESlg==", + "dev": true, + "dependencies": { + "convert-source-map": "^1.1.0", + "fs.realpath": "^1.0.0", + "object-assign": "^4.1.0", + "semver": "^6.1.0", + "through2": "^2.0.0", + "tsconfig": "^5.0.3" + }, + "engines": { + "node": ">=0.12" + }, + "peerDependencies": { + "browserify": ">= 10.x", + "typescript": ">= 2.8" + } + }, + "node_modules/tsify/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true, + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + }, + "bin": { + "undeclared-identifiers": "bin.js" + } + }, + "node_modules/undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/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, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-source-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-2.0.0.tgz", + "integrity": "sha1-84pa+53R6Ttl1VBGmsYYKsT1S44=", + "dev": true, + "dependencies": { + "through2": "^2.0.3", + "vinyl": "^2.1.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/watchify": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.1.tgz", + "integrity": "sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "browserify": "^16.1.0", + "chokidar": "^2.1.1", + "defined": "^1.0.0", + "outpipe": "^1.1.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "watchify": "bin/cmd.js" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "node_modules/yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "node_modules/yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + }, + "dependencies": { + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "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-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "dev": true + }, + "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" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "requires": { + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "JSONStream": "^1.0.3", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify": { + "version": "16.5.1", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.1.tgz", + "integrity": "sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A==", + "dev": true, + "requires": { + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "JSONStream": "^1.0.3", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.0.0", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "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 + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "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" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "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 + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, + "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 + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "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" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "deps-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", + "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.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" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.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": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "gulp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", + "integrity": "sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y=", + "dev": true, + "requires": { + "glob-watcher": "^5.0.0", + "gulp-cli": "^2.0.0", + "undertaker": "^1.0.0", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + } + } + } + }, + "gulp-typescript": { + "version": "6.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", + "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0", + "vinyl-fs": "^3.0.3" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "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.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true, + "requires": { + "source-map": "~0.5.3" + } + }, + "insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "dev": true, + "requires": { + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "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" + } + } + } + }, + "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-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "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-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "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" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "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" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "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 + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "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.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "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.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "module-deps": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", + "dev": true, + "requires": { + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "requires": { + "resolve": "^1.17.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "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" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "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 + }, + "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-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "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" + } + } + } + }, + "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-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "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" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "outpipe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true, + "requires": { + "shell-quote": "^1.4.2" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "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" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "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-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "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" + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "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" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "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": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "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 + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "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-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shasum-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", + "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", + "dev": true, + "requires": { + "fast-safe-stringify": "^2.0.7" + } + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "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" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-http": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", + "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "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" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "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-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "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" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "tsconfig": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-5.0.3.tgz", + "integrity": "sha1-X0J45wGACWeo/Dg/0ZZIh48qbjo=", + "dev": true, + "requires": { + "any-promise": "^1.3.0", + "parse-json": "^2.2.0", + "strip-bom": "^2.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "tsify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tsify/-/tsify-5.0.0.tgz", + "integrity": "sha512-Dt8RL7mSz39PMOIAthxQfM0OpqmasXyWOrI0U260OF+tdz7CJV1jhP2X80aXmMP/8Y2928rRq9rESXY0xzESlg==", + "dev": true, + "requires": { + "convert-source-map": "^1.1.0", + "fs.realpath": "^1.0.0", + "object-assign": "^4.1.0", + "semver": "^6.1.0", + "through2": "^2.0.0", + "tsconfig": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "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 + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "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" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-source-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-2.0.0.tgz", + "integrity": "sha1-84pa+53R6Ttl1VBGmsYYKsT1S44=", + "dev": true, + "requires": { + "through2": "^2.0.3", + "vinyl": "^2.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "watchify": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.1.tgz", + "integrity": "sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "browserify": "^16.1.0", + "chokidar": "^2.1.1", + "defined": "^1.0.0", + "outpipe": "^1.1.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "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" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + } +} diff --git a/hw3/package.json b/hw3/package.json new file mode 100644 index 0000000..082dd9d --- /dev/null +++ b/hw3/package.json @@ -0,0 +1,21 @@ +{ + "name": "wolfie2d", + "version": "1.0.0", + "description": "A game engine written in TypeScript", + "main": "./dist/main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Joe Weaver", + "license": "ISC", + "devDependencies": { + "browserify": "^16.5.1", + "fancy-log": "^1.3.3", + "gulp": "^4.0.0", + "gulp-typescript": "^6.0.0-alpha.1", + "tsify": "^5.0.0", + "typescript": "^3.9.7", + "vinyl-source-stream": "^2.0.0", + "watchify": "^3.11.1" + } +} diff --git a/hw3/readme.md b/hw3/readme.md new file mode 100644 index 0000000..641825d --- /dev/null +++ b/hw3/readme.md @@ -0,0 +1,6 @@ +# Game Engine +## How to transpile and run + +Start gulp by just running `gulp` in the console. Start the code by running `dist/main.js` with Web Server for Chrome or a similar product. Anytime you save, gulp should recompile the code automatically. + +Setup follows [this helpful guide from TypeScript] (https://www.typescriptlang.org/docs/handbook/gulp.html) (Up through Watchify). \ No newline at end of file diff --git a/hw3/src/Wolfie2D/AI/AIManager.ts b/hw3/src/Wolfie2D/AI/AIManager.ts new file mode 100644 index 0000000..026e0c6 --- /dev/null +++ b/hw3/src/Wolfie2D/AI/AIManager.ts @@ -0,0 +1,65 @@ +import Actor from "../DataTypes/Interfaces/Actor"; +import Updateable from "../DataTypes/Interfaces/Updateable"; +import AI from "../DataTypes/Interfaces/AI"; +import Map from "../DataTypes/Map"; + +/** + * A manager class for all of the AI in a scene. + * Keeps a list of registered actors and handles AI generation for actors. + */ +export default class AIManager implements Updateable { + /** The array of registered actors */ + actors: Array; + /** Maps AI names to their constructors */ + registeredAI: Map; + + constructor(){ + this.actors = new Array(); + this.registeredAI = new Map(); + } + + /** + * Registers an actor with the AIManager + * @param actor The actor to register + */ + registerActor(actor: Actor): void { + this.actors.push(actor); + } + + removeActor(actor: Actor): void { + let index = this.actors.indexOf(actor); + + if(index !== -1){ + this.actors.splice(index, 1); + } + } + + /** + * Registers an AI with the AIManager for use later on + * @param name The name of the AI to register + * @param constr The constructor for the AI + */ + registerAI(name: string, constr: new () => T ): void { + this.registeredAI.add(name, constr); + } + + /** + * Generates an AI instance from its name + * @param name The name of the AI to add + * @returns A new AI instance + */ + generateAI(name: string): AI { + if(this.registeredAI.has(name)){ + return new (this.registeredAI.get(name))(); + } else { + throw `Cannot create AI with name ${name}, no AI with that name is registered`; + } + } + + update(deltaT: number): void { + // Run the ai for every active actor + this.actors.forEach(actor => { if(actor.aiActive) actor.ai.update(deltaT) }); + } +} + +type AIConstructor = new () => T; \ No newline at end of file diff --git a/hw3/src/Wolfie2D/AI/ControllerAI.ts b/hw3/src/Wolfie2D/AI/ControllerAI.ts new file mode 100644 index 0000000..005e375 --- /dev/null +++ b/hw3/src/Wolfie2D/AI/ControllerAI.ts @@ -0,0 +1,21 @@ +import AI from "../DataTypes/Interfaces/AI"; +import GameEvent from "../Events/GameEvent"; +import GameNode from "../Nodes/GameNode"; + +export default abstract class ControllerAI implements AI { + /** The owner of this controller */ + owner: GameNode; + + /** Removes the instance of the owner of this AI */ + destroy(): void { + delete this.owner; + } + + abstract initializeAI(owner: GameNode, options: Record): void; + + abstract activate(options: Record): void; + + abstract handleEvent(event: GameEvent): void; + + abstract update(deltaT: number): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/AI/StateMachineAI.ts b/hw3/src/Wolfie2D/AI/StateMachineAI.ts new file mode 100644 index 0000000..b3bcd9f --- /dev/null +++ b/hw3/src/Wolfie2D/AI/StateMachineAI.ts @@ -0,0 +1,24 @@ +import AI from "../DataTypes/Interfaces/AI"; +import StateMachine from "../DataTypes/State/StateMachine"; +import GameNode from "../Nodes/GameNode"; + +/** + * A version of a @reference[StateMachine] that is configured to work as an AI controller for a @reference[GameNode] + */ +export default class StateMachineAI extends StateMachine implements AI { + /** The GameNode that uses this StateMachine for its AI */ + protected owner: GameNode; + + // @implemented + initializeAI(owner: GameNode, config: Record): void {} + + // @implemented + destroy(){ + // Get rid of our reference to the owner + delete this.owner; + this.receiver.destroy(); + } + + // @implemented + activate(options: Record): void {} +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Collection.ts b/hw3/src/Wolfie2D/DataTypes/Collection.ts new file mode 100644 index 0000000..25b0635 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Collection.ts @@ -0,0 +1,17 @@ +// @ignorePage + +/** + * An interface for all iterable data custom data structures + */ +export default interface Collection { + /** + * Iterates through all of the items in this data structure. + * @param func The function to evaluate of every item in the collection + */ + forEach(func: Function): void; + + /** + * Clears the contents of the data structure + */ + clear(): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Functions/NullFunc.ts b/hw3/src/Wolfie2D/DataTypes/Functions/NullFunc.ts new file mode 100644 index 0000000..9808300 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Functions/NullFunc.ts @@ -0,0 +1,8 @@ +// @ignorePage + +/** + * A placeholder function for No Operation. Does nothing + */ +const NullFunc = () => {}; + +export default NullFunc; \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Graphs/EdgeNode.ts b/hw3/src/Wolfie2D/DataTypes/Graphs/EdgeNode.ts new file mode 100644 index 0000000..3011ad9 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Graphs/EdgeNode.ts @@ -0,0 +1,22 @@ +/** + * A linked-list for the edges in a @reference[Graph]. + */ +export default class EdgeNode { + /** The node in the Graph this edge connects to */ + y: number; + /** The weight of this EdgeNode */ + weight: number; + /** The next EdgeNode in the linked-list */ + next: EdgeNode; + + /** + * Creates a new EdgeNode + * @param index The index of the node this edge connects to + * @param weight The weight of this edge + */ + constructor(index: number, weight?: number){ + this.y = index; + this.next = null; + this.weight = weight ? weight : 1; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Graphs/Graph.ts b/hw3/src/Wolfie2D/DataTypes/Graphs/Graph.ts new file mode 100644 index 0000000..21168c2 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Graphs/Graph.ts @@ -0,0 +1,145 @@ +import EdgeNode from "./EdgeNode"; + +export const MAX_V = 100; + +/** + * An implementation of a graph data structure using edge lists. Inspired by The Algorithm Design Manual. + */ +export default class Graph { + /** An array of edges at the node specified by the index */ + edges: Array; + /** An array representing the degree of the node specified by the index */ + degree: Array; + /** The number of vertices in the graph */ + numVertices: number; + /** The number of edges in the graph */ + numEdges: number; + /** Whether or not the graph is directed */ + directed: boolean; + /** Whether or not the graph is weighted */ + weighted: boolean; + + /** + * Constructs a new graph + * @param directed Whether or not this graph is directed + */ + constructor(directed: boolean = false){ + this.directed = directed; + this.weighted = false; + + this.numVertices = 0; + this.numEdges = 0; + + this.edges = new Array(MAX_V); + this.degree = new Array(MAX_V); + } + + /** Adds a node to this graph and returns the index of it + * @returns The index of the new node + */ + addNode(): number { + this.numVertices++; + return this.numVertices; + } + + /** Adds an edge between node x and y, with an optional weight + * @param x The index of the start of the edge + * @param y The index of the end of the edge + * @param weight The optional weight of the new edge + */ + addEdge(x: number, y: number, weight?: number): void { + let edge = new EdgeNode(y, weight); + + + + if(this.edges[x]){ + edge.next = this.edges[x]; + } + + this.edges[x] = edge; + + if(!this.directed){ + edge = new EdgeNode(x, weight); + + if(this.edges[y]){ + edge.next = this.edges[y]; + } + + this.edges[y] = edge; + } + + this.numEdges += 1; + } + + /** + * Checks whether or not an edge exists between two nodes. + * This check is directional if this is a directed graph. + * @param x The first node + * @param y The second node + * @returns true if an edge exists, false otherwise + */ + edgeExists(x: number, y: number): boolean { + let edge = this.edges[x]; + + while(edge !== null){ + if(edge.y === y){ + return true; + } + edge = edge.next; + } + } + + /** + * Gets the edge list associated with node x + * @param x The index of the node + * @returns The head of a linked-list of edges + */ + getEdges(x: number): EdgeNode { + return this.edges[x]; + } + + /** + * Gets the degree associated with node x + * @param x The index of the node + */ + getDegree(x: number): number { + return this.degree[x]; + } + + /** + * Converts the specifed node into a string + * @param index The index of the node to convert to a string + * @returns The string representation of the node: "Node x" + */ + protected nodeToString(index: number): string { + return "Node " + index; + } + + /** + * Converts the Graph into a string format + * @returns The graph as a string + */ + toString(): string { + let retval = ""; + + for(let i = 0; i < this.numVertices; i++){ + let edge = this.edges[i]; + let edgeStr = ""; + while(edge !== null){ + edgeStr += edge.y.toString(); + if(this.weighted){ + edgeStr += " (" + edge.weight + ")"; + } + if(edge.next !== null){ + edgeStr += ", "; + } + + edge = edge.next; + } + + retval += this.nodeToString(i) + ": " + edgeStr + "\n"; + } + + return retval; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Graphs/PositionGraph.ts b/hw3/src/Wolfie2D/DataTypes/Graphs/PositionGraph.ts new file mode 100644 index 0000000..cca9d77 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Graphs/PositionGraph.ts @@ -0,0 +1,94 @@ +import Graph, { MAX_V } from "./Graph"; +import Vec2 from "../Vec2"; +import DebugRenderable from "../Interfaces/DebugRenderable"; + +/** + * An extension of Graph that has nodes with positions in 2D space. + * This is a weighted graph (though not inherently directd) +*/ +export default class PositionGraph extends Graph implements DebugRenderable { + /** An array of the positions of the nodes in this graph */ + positions: Array; + + /** + * Createes a new PositionGraph + * @param directed Whether or not this graph is directed + */ + constructor(directed: boolean = false){ + super(directed); + this.positions = new Array(MAX_V); + } + + /** + * Adds a positioned node to this graph + * @param position The position of the node to add + * @returns The index of the added node + */ + addPositionedNode(position: Vec2): number { + this.positions[this.numVertices] = position; + return this.addNode(); + } + + /** + * Changes the position of a node. + * Automatically adjusts the weights of the graph tied to this node. + * As such, be warned that this function has an O(n + m) running time, and use it sparingly. + * @param index The index of the node + * @param position The new position of the node + */ + setNodePosition(index: number, position: Vec2): void { + this.positions[index] = position; + + // Recalculate all weights associated with this index + for(let i = 0; i < this.numEdges; i++){ + + let edge = this.edges[i]; + + while(edge !== null){ + // If this node is on either side of the edge, recalculate weight + if(i === index || edge.y === index){ + edge.weight = this.positions[i].distanceTo(this.positions[edge.y]); + } + + edge = edge.next; + } + } + } + + /** + * Gets the position of a node + * @param index The index of the node + * @returns The position of the node + */ + getNodePosition(index: number): Vec2 { + return this.positions[index]; + } + + /** + * Adds an edge to this graph between node x and y. + * Automatically calculates the weight of the edge as the distance between the nodes. + * @param x The beginning of the edge + * @param y The end of the edge + */ + addEdge(x: number, y: number): void { + if(!this.positions[x] || !this.positions[y]){ + throw "Can't add edge to un-positioned node!"; + } + + // Weight is the distance between the nodes + let weight = this.positions[x].distanceTo(this.positions[y]); + + super.addEdge(x, y, weight); + } + + // @override + protected nodeToString(index: number): string { + return "Node " + index + " - " + this.positions[index].toString(); + } + + debugRender = (): void => { + // for(let point of this.positions){ + // ctx.fillRect((point.x - origin.x - 4)*zoom, (point.y - origin.y - 4)*zoom, 8, 8); + // } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/AI.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/AI.ts new file mode 100644 index 0000000..625358b --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/AI.ts @@ -0,0 +1,20 @@ +import GameEvent from "../../Events/GameEvent"; +import GameNode from "../../Nodes/GameNode"; +import Updateable from "./Updateable"; + +/** + * Defines a controller for a bot or a human. Must be able to update + */ +export default interface AI extends Updateable { + /** Initializes the AI with the actor and any additional config */ + initializeAI(owner: GameNode, options: Record): void; + + /** Clears references from to the owner */ + destroy(): void; + + /** Activates this AI from a stopped state and allows variables to be passed in */ + activate(options: Record): void; + + /** Handles events from the Actor */ + handleEvent(event: GameEvent): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Actor.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Actor.ts new file mode 100644 index 0000000..c26f62e --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Actor.ts @@ -0,0 +1,40 @@ +import NavigationPath from "../../Pathfinding/NavigationPath"; +import AI from "./AI"; + +/** + * A game object that has an AI and can perform its own actions every update cycle + */ +export default interface Actor { + /** The AI of the actor */ + ai: AI; + + /** The activity status of the actor */ + aiActive: boolean; + + /** The path that navigation will follow */ + path: NavigationPath; + + /** A flag representing whether or not the actor is currently pathfinding */ + pathfinding: boolean; + + /** + * Adds an AI to this Actor. + * @param ai The name of the AI, or the actual AI, to add to the Actor. + * @param options The options to give to the AI for initialization. + */ + addAI(ai: string | (new () => T), options: Record): void; + + /** + * Sets the AI to start/stop for this Actor. + * @param active The new active status of the AI. + * @param options An object that allows options to be pased to the activated AI + */ + setAIActive(active: boolean, options: Record): void; + + /** + * Moves this GameNode along a path + * @param speed The speed to move with + * @param path The path we're moving along + */ + moveOnPath(speed: number, path: NavigationPath): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/DebugRenderable.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/DebugRenderable.ts new file mode 100644 index 0000000..b440c7c --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/DebugRenderable.ts @@ -0,0 +1,7 @@ +/** + * Represents a game object that can be rendered in Debug mode + */ +export default interface DebugRenderable { + /** Renders the debugging information for this object. */ + debugRender(): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Navigable.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Navigable.ts new file mode 100644 index 0000000..8f80c0f --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Navigable.ts @@ -0,0 +1,12 @@ +import Vec2 from "../Vec2"; +import NavigationPath from "../../Pathfinding/NavigationPath"; + +/** Represents an entity that can be navigated on. */ +export default interface Navigable { + /** + * Gets a new navigation path based on this Navigable object. + * @param fromPosition The position to start navigation from. + * @param toPosition The position to navigate to. + */ + getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Physical.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Physical.ts new file mode 100644 index 0000000..989a2f5 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Physical.ts @@ -0,0 +1,120 @@ +import Shape from "../Shapes/Shape"; +import AABB from "../Shapes/AABB"; +import Vec2 from "../Vec2"; +import Map from "../Map"; + +/** + * Describes an object that can opt into physics. + */ +export default interface Physical { + /** A flag for whether or not this object has initialized game physics. */ + hasPhysics: boolean; + + /** Represents whether the object is moving or not. */ + moving: boolean; + + /** Represent whether the object is frozen from moving or not. */ + frozen: boolean; + + /** Represents whether the object is on the ground or not. */ + onGround: boolean; + + /** Reprsents whether the object is on the wall or not. */ + onWall: boolean; + + /** Reprsents whether the object is on the ceiling or not. */ + onCeiling: boolean; + + /** Represnts whether this object has active physics or not. */ + active: boolean; + + /** The shape of the collider for this physics object. */ + collisionShape: Shape; + + /** The offset of the collision shape from the center of the node */ + colliderOffset: Vec2; + + /** Represents whether this object can move or not. */ + isStatic: boolean; + + /** Represents whether this object is collidable (solid) or not. */ + isCollidable: boolean; + + /** Represnts whether this object is a trigger or not. */ + isTrigger: boolean; + + /** The trigger mask for this node */ + triggerMask: number; + + /** Events to trigger for collision enters. */ + triggerEnters: Array; + + /** Events to trigger for collision exits */ + triggerExits: Array; + + /** A vector that allows velocity to be passed to the physics engine */ + _velocity: Vec2; + + /** The rectangle swept by the movement of this object, if dynamic */ + sweptRect: AABB; + + /** A boolean representing whether or not the node just collided with the tilemap */ + collidedWithTilemap: boolean; + + /** The physics group this node belongs to */ + group: number; + + isPlayer: boolean; + + isColliding: boolean; + + /*---------- FUNCTIONS ----------*/ + + /** + * Tells the physics engine to handle a move by this object. + * @param velocity The velocity with which to move the object. + */ + move(velocity: Vec2): void; + + /** + * The move actually done by the physics engine after collision checks are done. + * @param velocity The velocity with which the object will move. + */ + finishMove(): void; + + /** + * Adds physics to this object + * @param collisionShape The shape of this collider for this object + * @param isCollidable Whether this object will be able to collide with other objects + * @param isStatic Whether this object will be static or not + */ + addPhysics(collisionShape?: Shape, colliderOffset?: Vec2, isCollidable?: boolean, isStatic?: boolean): void; + + /** Removes this object from the physics system */ + removePhysics(): void; + + /** Prevents this object from participating in all collisions and triggers. It can still move. */ + disablePhysics(): void; + + /** Enables this object to participate in collisions and triggers. This is only necessary if disablePhysics was called */ + enablePhysics(): void; + + /** + * Sets this object to be a trigger for a specific group + * @param group The name of the group that activates the trigger + * @param onEnter The name of the event to send when this trigger is activated + * @param onExit The name of the event to send when this trigger stops being activated + */ + setTrigger(group: string, onEnter: string, onExit: string): void; + + /** + * Sets the physics group of this node + * @param group The name of the group + */ + setGroup(group: string): void; + + /** + * If used before "move()", it will tell you the velocity of the node after its last movement + */ + getLastVelocity(): Vec2; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Positioned.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Positioned.ts new file mode 100644 index 0000000..9da68e8 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Positioned.ts @@ -0,0 +1,10 @@ +import Vec2 from "../Vec2"; + +/** An object that has a position */ +export default interface Positioned { + /** The center of this object. */ + position: Vec2; + + /** The center of this object relative to the viewport. */ + readonly relativePosition: Vec2; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Region.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Region.ts new file mode 100644 index 0000000..019fce3 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Region.ts @@ -0,0 +1,21 @@ +import Vec2 from "../Vec2"; +import AABB from "../Shapes/AABB"; + +/** An object that is a region, with a size, scale, and boundary. */ +export default interface Region { + /** The size of this object. */ + size: Vec2; + + /** The scale of this object. */ + scale: Vec2; + + /** The size of the object taking into account the zoom and scale */ + readonly sizeWithZoom: Vec2; + + /** The bounding box of this object. */ + boundary: AABB; +} + +export function isRegion(arg: any): boolean { + return arg && arg.size && arg.scale && arg.boundary; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Unique.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Unique.ts new file mode 100644 index 0000000..7e04e25 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Unique.ts @@ -0,0 +1,7 @@ +/** + * Represents an object with a unique id + */ +export default interface Unique { + /** The unique id of this object. */ + id: number; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Interfaces/Updateable.ts b/hw3/src/Wolfie2D/DataTypes/Interfaces/Updateable.ts new file mode 100644 index 0000000..0df2cf6 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Interfaces/Updateable.ts @@ -0,0 +1,8 @@ +/** Represents a game object that can be updated */ +export default interface Updateable { + /** + * Updates this object. + * @param deltaT The timestep of the update. + */ + update(deltaT: number): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/List.ts b/hw3/src/Wolfie2D/DataTypes/List.ts new file mode 100644 index 0000000..2a86182 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/List.ts @@ -0,0 +1,63 @@ +import Collection from "./Collection"; + +/** + * A doubly linked list + */ +export default class List implements Collection { + + private head: ListItem; + private tail: ListItem; + private _size: number; + + constructor(){ + this.head = null; + this.tail = null; + this._size = 0; + } + + get size(): number { + return this._size; + } + + add(value: T){ + if(this._size === 0){ + // There were no items in the list previously, so head and tail are the same + this.head = new ListItem(value, null, null); + this.tail = this.head; + } else { + this.tail.next = new ListItem(value, this.tail, null); + this.tail = this.tail.next; + } + + // Increment the size + this._size += 1; + } + + forEach(func: Function): void { + let p = this.head; + + while(p !== null){ + func(p.value); + p = p.next; + } + } + + clear(): void { + this.head = null + this.tail = null + this._size = 0; + } + +} + +class ListItem { + value: T; + next: ListItem; + prev: ListItem; + + constructor(value: T, next: ListItem, prev: ListItem){ + this.value = value; + this.next = next; + this.prev = prev; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Map.ts b/hw3/src/Wolfie2D/DataTypes/Map.ts new file mode 100644 index 0000000..7937de6 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Map.ts @@ -0,0 +1,87 @@ +import Collection from "./Collection"; + +/** + * Associates strings with elements of type T + */ +export default class Map implements Collection { + private map: Record; + + /** Creates a new map */ + constructor(){ + this.map = {}; + } + + /** + * Adds a value T stored at a key. + * @param key The key of the item to be stored + * @param value The item to be stored + */ + add(key: string, value: T): void { + this.map[key] = value; + } + + /** + * Get the value associated with a key. + * @param key The key of the item + * @returns The item at the key or undefined + */ + get(key: string): T { + return this.map[key]; + } + + /** + * An alias of add. Sets the value stored at key to the new specified value + * @param key The key of the item to be stored + * @param value The item to be stored + */ + set(key: string, value: T): void { + this.add(key, value); + } + + /** + * Returns true if there is a value stored at the specified key, false otherwise. + * @param key The key to check + * @returns A boolean representing whether or not there is an item at the given key. + */ + has(key: string): boolean { + return this.map[key] !== undefined; + } + + /** + * Returns an array of all of the keys in this map. + * @returns An array containing all keys in the map. + */ + keys(): Array { + return Object.keys(this.map); + } + + // @implemented + forEach(func: (key: string) => void): void { + Object.keys(this.map).forEach(key => func(key)); + } + + /** + * Deletes an item associated with a key + * @param key The key at which to delete an item + */ + delete(key: string): void { + delete this.map[key]; + } + + // @implemented + clear(): void { + this.forEach(key => delete this.map[key]); + } + + /** + * Converts this map to a string representation. + * @returns The string representation of this map. + */ + toString(): string { + let str = ""; + + this.forEach((key) => str += key + " -> " + this.get(key).toString() + "\n"); + + return str; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Mat4x4.ts b/hw3/src/Wolfie2D/DataTypes/Mat4x4.ts new file mode 100644 index 0000000..c0765bb --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Mat4x4.ts @@ -0,0 +1,167 @@ +import Vec2 from "./Vec2"; + +/** A 4x4 matrix0 */ +export default class Mat4x4 { + private mat: Float32Array; + + constructor(){ + this.mat = new Float32Array([ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + ]); + } + + // Static members + static get IDENTITY(): Mat4x4 { + return new Mat4x4().identity(); + } + + static get ZERO(): Mat4x4 { + return new Mat4x4().zero(); + } + + // Accessors + set _00(x: number) { + this.mat[0] = x; + } + + set(col: number, row: number, value: number): Mat4x4 { + if(col < 0 || col > 3 || row < 0 || row > 3){ + throw `Error - index (${col}, ${row}) is out of bounds for Mat4x4` + } + this.mat[row*4 + col] = value; + + return this; + } + + get(col: number, row: number): number { + return this.mat[row*4 + col]; + } + + setAll(...items: Array): Mat4x4 { + this.mat.set(items); + return this; + } + + identity(): Mat4x4 { + return this.setAll( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ) + } + + zero(): Mat4x4 { + return this.setAll( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + ); + } + + /** + * Makes this Mat4x4 a rotation matrix of the specified number of radians ccw + * @param zRadians The number of radians to rotate + * @returns this Mat4x4 + */ + rotate(zRadians: number): Mat4x4 { + return this.setAll( + Math.cos(zRadians), -Math.sin(zRadians), 0, 0, + Math.sin(zRadians), Math.cos(zRadians), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + } + + /** + * Turns this Mat4x4 into a translation matrix of the specified translation + * @param translation The translation in x and y + * @returns this Mat4x4 + */ + translate(translation: Vec2 | Float32Array): Mat4x4 { + // If translation is a vec, get its array + if(translation instanceof Vec2){ + translation = translation.toArray(); + } + + return this.setAll( + 1, 0, 0, translation[0], + 0, 1, 0, translation[1], + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + } + + scale(scale: Vec2 | Float32Array | number): Mat4x4 { + // Make sure scale is a float32Array + if(scale instanceof Vec2){ + scale = scale.toArray(); + } else if(!(scale instanceof Float32Array)){ + scale = new Float32Array([scale, scale]); + } + + return this.setAll( + scale[0], 0, 0, 0, + 0, scale[1], 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + } + + /** + * Returns a new Mat4x4 that represents the right side multiplication THIS x OTHER + * @param other The other Mat4x4 to multiply by + * @returns a new Mat4x4 containing the product of these two Mat4x4s + */ + mult(other: Mat4x4, out?: Mat4x4): Mat4x4 { + let temp = new Float32Array(16); + + for(let i = 0; i < 4; i++){ + for(let j = 0; j < 4; j++){ + let value = 0; + for(let k = 0; k < 4; k++){ + value += this.get(k, i) * other.get(j, k); + } + temp[j*4 + i] = value; + } + } + + if(out !== undefined){ + return out.setAll(...temp); + } else { + return new Mat4x4().setAll(...temp); + } + } + + /** + * Multiplies all given matricies in order. e.g. MULT(A, B, C) -> A*B*C + * @param mats A list of Mat4x4s to multiply in order + * @returns A new Mat4x4 holding the result of the operation + */ + static MULT(...mats: Array): Mat4x4 { + // Create a new array + let temp = Mat4x4.IDENTITY; + + // Multiply by every array in order, in place + for(let i = 0; i < mats.length; i++){ + temp.mult(mats[i], temp); + } + + return temp; + } + + toArray(): Float32Array { + return this.mat; + } + + toString(): string { + return `|${this.mat[0].toFixed(2)}, ${this.mat[1].toFixed(2)}, ${this.mat[2].toFixed(2)}, ${this.mat[3].toFixed(2)}|\n` + + `|${this.mat[4].toFixed(2)}, ${this.mat[5].toFixed(2)}, ${this.mat[6].toFixed(2)}, ${this.mat[7].toFixed(2)}|\n` + + `|${this.mat[8].toFixed(2)}, ${this.mat[9].toFixed(2)}, ${this.mat[10].toFixed(2)}, ${this.mat[11].toFixed(2)}|\n` + + `|${this.mat[12].toFixed(2)}, ${this.mat[13].toFixed(2)}, ${this.mat[14].toFixed(2)}, ${this.mat[15].toFixed(2)}|`; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts b/hw3/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts new file mode 100644 index 0000000..1d2b94e --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Physics/AreaCollision.ts @@ -0,0 +1,40 @@ +import Physical from "../Interfaces/Physical"; +import AABB from "../Shapes/AABB"; +import Vec2 from "../Vec2"; +import Hit from "./Hit"; + +/** + * A class that contains the area of overlap of two colliding objects to allow for sorting by the physics system. + */ +export default class AreaCollision { + /** The area of the overlap for the colliding objects */ + area: number; + + /** The AABB of the other collider in this collision */ + collider: AABB; + + /** Type of the collision */ + type: string; + + /** Ther other object in the collision */ + other: Physical; + + /** The tile, if this was a tilemap collision */ + tile: Vec2; + + /** The physics hit for this object */ + hit: Hit; + + /** + * Creates a new AreaCollision object + * @param area The area of the collision + * @param collider The other collider + */ + constructor(area: number, collider: AABB, other: Physical, type: string, tile: Vec2){ + this.area = area; + this.collider = collider; + this.other = other; + this.type = type; + this.tile = tile; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Physics/Hit.ts b/hw3/src/Wolfie2D/DataTypes/Physics/Hit.ts new file mode 100644 index 0000000..7ee1a6a --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Physics/Hit.ts @@ -0,0 +1,18 @@ +import Vec2 from "../Vec2"; + +/** + * An object representing the data collected from a physics hit between two geometric objects. + * Inspired by the helpful collision documentation @link(here)(https://noonat.github.io/intersect/). + */ +export default class Hit { + /** The time of the collision. Only numbers 0 through 1 happen in this frame. */ + time: number; + /** The near times of the collision */ + nearTimes: Vec2 = Vec2.ZERO; + /** The position of the collision */ + pos: Vec2 = Vec2.ZERO; + /** The overlap distance of the hit */ + delta: Vec2 = Vec2.ZERO; + /** The normal vector of the hit */ + normal: Vec2 = Vec2.ZERO; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/QuadTree.ts b/hw3/src/Wolfie2D/DataTypes/QuadTree.ts new file mode 100644 index 0000000..25c77d5 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/QuadTree.ts @@ -0,0 +1,156 @@ +import Vec2 from "./Vec2"; +import Collection from "./Collection"; +import AABB from "./Shapes/AABB" +import Positioned from "./Interfaces/Positioned"; + +// TODO - Make max depth + +// @ignorePage + +/** + * Primarily used to organize the scene graph + */ +export default class QuadTree implements Collection { + /** + * The center of this quadtree + */ + protected boundary: AABB; + + /** + * The number of elements this quadtree root can hold before splitting + */ + protected capacity: number; + + /** + * The maximum height of the quadtree from this root + */ + protected maxDepth: number; + + /** + * Represents whether the quadtree is a root or a leaf + */ + protected divided: boolean; + + /** + * The array of the items in this quadtree + */ + protected items: Array; + + // The child quadtrees of this one + protected nw: QuadTree; + protected ne: QuadTree; + protected sw: QuadTree; + protected se: QuadTree; + + constructor(center: Vec2, size: Vec2, maxDepth?: number, capacity?: number){ + this.boundary = new AABB(center, size); + this.maxDepth = maxDepth !== undefined ? maxDepth : 10 + this.capacity = capacity ? capacity : 10; + + // If we're at the bottom of the tree, don't set a max size + if(this.maxDepth === 0){ + this.capacity = Infinity; + } + + this.divided = false; + this.items = new Array(); + } + + /** + * Inserts a new item into this quadtree. Defers to children if this quadtree is divided + * or divides the quadtree if capacity is exceeded with this add. + * @param item The item to add to the quadtree + */ + insert(item: T){ + // If the item is inside of the bounds of this quadtree + if(this.boundary.containsPointSoft(item.position)){ + if(this.divided){ + // Defer to the children + this.deferInsert(item); + } else if (this.items.length < this.capacity){ + // Add to this items list + this.items.push(item); + } else { + // We aren't divided, but are at capacity - divide + this.subdivide(); + this.deferInsert(item); + this.divided = true; + } + } + } + + /** + * Divides this quadtree up into 4 smaller ones - called through insert. + */ + protected subdivide(): void { + let x = this.boundary.x; + let y = this.boundary.y; + let hw = this.boundary.hw; + let hh = this.boundary.hh; + + this.nw = new QuadTree(new Vec2(x-hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1); + this.ne = new QuadTree(new Vec2(x+hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1) + this.sw = new QuadTree(new Vec2(x-hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1) + this.se = new QuadTree(new Vec2(x+hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1) + + this.distributeItems(); + } + + /** + * Distributes the items of this quadtree into its children. + */ + protected distributeItems(): void { + this.items.forEach(item => this.deferInsert(item)); + + // Delete the items from this array + this.items.forEach((item, index) => delete this.items[index]); + this.items.length = 0; + } + + /** + * Defers this insertion to the children of this quadtree + * @param item + */ + protected deferInsert(item: T): void { + this.nw.insert(item); + this.ne.insert(item); + this.sw.insert(item); + this.se.insert(item); + } + + /** + * Renders the quadtree for demo purposes. + * @param ctx + */ + public render_demo(ctx: CanvasRenderingContext2D): void { + ctx.strokeStyle = "#FF0000"; + ctx.strokeRect(this.boundary.x - this.boundary.hw, this.boundary.y - this.boundary.hh, 2*this.boundary.hw, 2*this.boundary.hh); + + if(this.divided){ + this.nw.render_demo(ctx); + this.ne.render_demo(ctx); + this.sw.render_demo(ctx); + this.se.render_demo(ctx); + } + } + + forEach(func: Function): void { + // If divided, send the call down + if(this.divided){ + this.nw.forEach(func); + this.ne.forEach(func); + this.sw.forEach(func); + this.se.forEach(func); + } else { + // Otherwise, iterate over items + for(let i = 0; i < this.items.length; i++){ + func(this.items[i]); + } + } + } + + clear(): void { + throw new Error("Method not implemented."); + } + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Queue.ts b/hw3/src/Wolfie2D/DataTypes/Queue.ts new file mode 100644 index 0000000..ad47adb --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Queue.ts @@ -0,0 +1,130 @@ +import Collection from "./Collection"; + +/** + * A FIFO queue with elements of type T + */ +export default class Queue implements Collection { + /** The maximum number of elements in the Queue */ + private readonly MAX_ELEMENTS: number; + + /** The internal representation of the queue */ + private q: Array; + + /** The head of the queue */ + private head: number; + + /** The tail of the queue */ + private tail: number; + + /** The current number of items in the queue */ + private size: number; + + /** + * Constructs a new queue + * @param maxElements The maximum size of the stack + */ + constructor(maxElements: number = 100){ + this.MAX_ELEMENTS = maxElements; + this.q = new Array(this.MAX_ELEMENTS); + this.head = 0; + this.tail = 0; + this.size = 0; + } + + /** + * Adds an item to the back of the queue + * @param item The item to add to the back of the queue + */ + enqueue(item: T): void{ + if((this.tail + 1) % this.MAX_ELEMENTS === this.head){ + throw new Error("Queue full - cannot add element"); + } + + this.size += 1; + this.q[this.tail] = item; + this.tail = (this.tail + 1) % this.MAX_ELEMENTS; + } + + /** + * Retrieves an item from the front of the queue + * @returns The item at the front of the queue + */ + dequeue(): T { + if(this.head === this.tail){ + throw new Error("Queue empty - cannot remove element"); + } + + + this.size -= 1; + let item = this.q[this.head]; + // Now delete the item + delete this.q[this.head]; + this.head = (this.head + 1) % this.MAX_ELEMENTS; + + return item; + } + + /** + * Returns the item at the front of the queue, but does not remove it + * @returns The item at the front of the queue + */ + peekNext(): T { + if(this.head === this.tail){ + throw "Queue empty - cannot get element" + } + + let item = this.q[this.head]; + + return item; + } + + /** + * Returns true if the queue has items in it, false otherwise + * @returns A boolean representing whether or not this queue has items + */ + hasItems(): boolean { + return this.head !== this.tail; + } + + /** + * Returns the number of elements in the queue. + * @returns The size of the queue + */ + getSize(): number { + return this.size; + } + + // @implemented + clear(): void { + this.forEach((item, index) => delete this.q[index]); + this.size = 0; + this.head = this.tail; + } + + // @implemented + forEach(func: (item: T, index?: number) => void): void { + let i = this.head; + while(i !== this.tail){ + func(this.q[i], i); + i = (i + 1) % this.MAX_ELEMENTS; + } + } + + /** + * Converts this queue into a string format + * @returns A string representing this queue + */ + toString(): string { + let retval = ""; + + this.forEach( (item, index) => { + let str = item.toString() + if(index !== 0){ + str += " -> " + } + retval = str + retval; + }); + + return "Top -> " + retval; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/RegionQuadTree.ts b/hw3/src/Wolfie2D/DataTypes/RegionQuadTree.ts new file mode 100644 index 0000000..b8d19fe --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/RegionQuadTree.ts @@ -0,0 +1,271 @@ +import Vec2 from "./Vec2"; +import Collection from "./Collection"; +import AABB from "./Shapes/AABB"; +import Region from "./Interfaces/Region"; +import Unique from "./Interfaces/Unique"; +import Map from "./Map"; + +/** + * A quadtree data structure implemented to work with regions rather than points. + * Elements in this quadtree have a position and an area, and thus can span multiple + * quadtree branches. + */ +export default class QuadTree implements Collection { + /** The center of this quadtree */ + protected boundary: AABB; + + /** The number of elements this quadtree root can hold before splitting */ + protected capacity: number; + + /** The maximum height of the quadtree from this root */ + protected maxDepth: number; + + /** Represents whether the quadtree is a root or a leaf */ + protected divided: boolean; + + /** The array of the items in this quadtree */ + protected items: Array; + + // The child quadtrees of this one + /** The top left child */ + protected nw: QuadTree; + /** The top right child */ + protected ne: QuadTree; + /** The bottom left child */ + protected sw: QuadTree; + /** The bottom right child */ + protected se: QuadTree; + + constructor(center: Vec2, size: Vec2, maxDepth?: number, capacity?: number){ + this.boundary = new AABB(center, size); + this.maxDepth = maxDepth !== undefined ? maxDepth : 10 + this.capacity = capacity ? capacity : 10; + + // If we're at the bottom of the tree, don't set a max size + if(this.maxDepth === 1){ + this.capacity = Infinity; + } + + this.divided = false; + this.items = new Array(); + + // Create all of the children for this quadtree if there are any + if(this.maxDepth > 1){ + let x = this.boundary.x; + let y = this.boundary.y; + let hw = this.boundary.hw; + let hh = this.boundary.hh; + + this.nw = new QuadTree(new Vec2(x-hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity); + this.ne = new QuadTree(new Vec2(x+hw/2, y-hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity); + this.sw = new QuadTree(new Vec2(x-hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity); + this.se = new QuadTree(new Vec2(x+hw/2, y+hh/2), new Vec2(hw/2, hh/2), this.maxDepth - 1, this.capacity); + } + } + + /** + * Inserts a new item into this quadtree. Defers to children if this quadtree is divided + * or divides the quadtree if capacity is exceeded with this add. + * @param item The item to add to the quadtree + */ + insert(item: T): void { + // If the item is inside of the bounds of this quadtree + if(this.boundary.overlaps(item.boundary)){ + if(this.divided){ + // Defer to the children + this.deferInsert(item); + } else if (this.items.length < this.capacity){ + // Add to this items list + this.items.push(item); + } else { + // We aren't divided, but are at capacity - divide + this.subdivide(); + this.deferInsert(item); + } + } + } + + /** + * Returns all items at this point. + * @param point The point to query at + * @returns A list of all elements in the quadtree that contain the specified point + */ + queryPoint(point: Vec2): Array { + // A matrix to keep track of our results + let results = new Array(); + + // A map to keep track of the items we've already found + let uniqueMap = new Map(); + + // Query and return + this._queryPoint(point, results, uniqueMap); + return results; + } + + // @ignoreFunction + /** + * A recursive function called by queryPoint + * @param point The point being queried + * @param results The results matrix + * @param uniqueMap A map that stores the unique ids of the results so we know what was already found + */ + protected _queryPoint(point: Vec2, results: Array, uniqueMap: Map): void { + // Does this quadtree even contain the point? + if(!this.boundary.containsPointSoft(point)) return; + + // If the matrix is divided, ask its children for results + if(this.divided){ + this.nw._queryPoint(point, results, uniqueMap); + this.ne._queryPoint(point, results, uniqueMap); + this.sw._queryPoint(point, results, uniqueMap); + this.se._queryPoint(point, results, uniqueMap); + } else { + // Otherwise, return a set of the items + for(let item of this.items){ + let id = item.id.toString(); + // If the item hasn't been found yet and it contains the point + if(!uniqueMap.has(id) && item.boundary.containsPoint(point)){ + // Add it to our found points + uniqueMap.add(id, item); + results.push(item); + } + } + } + } + + /** + * Returns all items in this region + * @param boundary The region to check + * @param inclusionCheck Allows for additional inclusion checks to further refine searches + * @returns A list of all elements in the specified region + */ + queryRegion(boundary: AABB): Array { + // A matrix to keep track of our results + let results = new Array(); + + // A map to keep track of the items we've already found + let uniqueMap = new Array(); + + // Query and return + this._queryRegion(boundary, results, uniqueMap); + return results; + } + + // @ignoreFunction + /** + * A recursive function called by queryPoint + * @param point The point being queried + * @param results The results matrix + * @param uniqueMap A map that stores the unique ids of the results so we know what was already found + */ + protected _queryRegion(boundary: AABB, results: Array, uniqueMap: Array): void { + // Does this quadtree even contain the point? + if(!this.boundary.overlaps(boundary)) return; + + // If the matrix is divided, ask its children for results + if(this.divided){ + this.nw._queryRegion(boundary, results, uniqueMap); + this.ne._queryRegion(boundary, results, uniqueMap); + this.sw._queryRegion(boundary, results, uniqueMap); + this.se._queryRegion(boundary, results, uniqueMap); + } else { + // Otherwise, return a set of the items + for(let item of this.items){ + // TODO - This is REALLY slow for some reason when we check for unique keys + + // let id = item.getId().toString(); + // // If the item hasn't been found yet and it contains the point + // if(!uniqueMap.has(id) && item.getBoundary().overlaps(boundary)){ + // // Add it to our found points + // uniqueMap.add(id, item); + // results.push(item); + // } + + // Maybe this is better? Just use a boolean array with no string nonsense? + if(item.id >= uniqueMap.length || !uniqueMap[item.id]){ + if(item.boundary.overlaps(boundary)){ + results.push(item); + uniqueMap[item.id] = true; + } + } + } + } + } + + /** + * Divides this quadtree up into 4 smaller ones - called through insert. + */ + protected subdivide(): void { + this.divided = true; + this.distributeItems(); + } + + /** + * Distributes the items of this quadtree into its children. + */ + protected distributeItems(): void { + this.items.forEach(item => this.deferInsert(item)); + + // Delete the items from this array + this.items.forEach((item, index) => delete this.items[index]); + this.items.length = 0; + } + + /** + * Defers this insertion to the children of this quadtree + * @param item The item to insert + */ + protected deferInsert(item: T): void { + this.nw.insert(item); + this.ne.insert(item); + this.sw.insert(item); + this.se.insert(item); + } + + public render_demo(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void { + ctx.strokeStyle = "#0000FF"; + ctx.strokeRect((this.boundary.x - this.boundary.hw - origin.x)*zoom, (this.boundary.y - this.boundary.hh - origin.y)*zoom, 2*this.boundary.hw*zoom, 2*this.boundary.hh*zoom); + + if(this.divided){ + this.nw.render_demo(ctx, origin, zoom); + this.ne.render_demo(ctx, origin, zoom); + this.sw.render_demo(ctx, origin, zoom); + this.se.render_demo(ctx, origin, zoom); + } + } + + // @implemented + forEach(func: Function): void { + // If divided, send the call down + if(this.divided){ + this.nw.forEach(func); + this.ne.forEach(func); + this.sw.forEach(func); + this.se.forEach(func); + } else { + // Otherwise, iterate over items + for(let i = 0; i < this.items.length; i++){ + func(this.items[i]); + } + } + } + + // @implemented + clear(): void { + if(this.nw){ + this.nw.clear(); + this.ne.clear(); + this.sw.clear(); + this.se.clear(); + } + + for(let item in this.items){ + delete this.items[item]; + } + + this.items.length = 0; + + this.divided = false; + } + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLGameTexture.ts b/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLGameTexture.ts new file mode 100644 index 0000000..c111c66 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLGameTexture.ts @@ -0,0 +1,5 @@ +export default class WebGLGameTexture { + webGLTextureId: number; + webGLTexture: WebGLTexture; + imageKey: string; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLProgramType.ts b/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLProgramType.ts new file mode 100644 index 0000000..6452dfa --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Rendering/WebGLProgramType.ts @@ -0,0 +1,29 @@ +/** A container for info about a webGL shader program */ +export default class WebGLProgramType { + /** A webGL program */ + program: WebGLProgram; + + /** A vertex shader */ + vertexShader: WebGLShader; + + /** A fragment shader */ + fragmentShader: WebGLShader; + + /** + * Deletes this shader program + */ + delete(gl: WebGLRenderingContext): void { + // Clean up all aspects of this program + if(this.program){ + gl.deleteProgram(this.program); + } + + if(this.vertexShader){ + gl.deleteShader(this.vertexShader); + } + + if(this.fragmentShader){ + gl.deleteShader(this.fragmentShader); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Shapes/AABB.ts b/hw3/src/Wolfie2D/DataTypes/Shapes/AABB.ts new file mode 100644 index 0000000..d80c9db --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Shapes/AABB.ts @@ -0,0 +1,339 @@ +import Shape from "./Shape"; +import Vec2 from "../Vec2"; +import MathUtils from "../../Utils/MathUtils"; +import Circle from "./Circle"; +import Hit from "../Physics/Hit"; + +/** + * An Axis-Aligned Bounding Box. In other words, a rectangle that is always aligned to the x-y grid. + * Inspired by the helpful collision documentation @link(here)(https://noonat.github.io/intersect/). + */ +export default class AABB extends Shape { + center: Vec2; + halfSize: Vec2; + + /** + * Creates a new AABB + * @param center The center of the AABB + * @param halfSize The half size of the AABB - The distance from the center to an edge in x and y + */ + constructor(center?: Vec2, halfSize?: Vec2){ + super(); + this.center = center ? center : new Vec2(0, 0); + this.halfSize = halfSize ? halfSize : new Vec2(0, 0); + } + + /** Returns a point representing the top left corner of the AABB */ + get topLeft(): Vec2 { + return new Vec2(this.left, this.top) + } + + /** Returns a point representing the top right corner of the AABB */ + get topRight(): Vec2 { + return new Vec2(this.right, this.top) + } + + /** Returns a point representing the bottom left corner of the AABB */ + get bottomLeft(): Vec2 { + return new Vec2(this.left, this.bottom) + } + + /** Returns a point representing the bottom right corner of the AABB */ + get bottomRight(): Vec2 { + return new Vec2(this.right, this.bottom) + } + + // @override + getBoundingRect(): AABB { + return this.clone(); + } + + // @override + getBoundingCircle(): Circle { + let r = Math.max(this.hw, this.hh) + return new Circle(this.center.clone(), r); + } + + // @deprecated + getHalfSize(): Vec2 { + return this.halfSize; + } + + // @deprecated + setHalfSize(halfSize: Vec2): void { + this.halfSize = halfSize; + } + + // TODO - move these all to the Shape class + /** + * A simple boolean check of whether this AABB contains a point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point + */ + containsPoint(point: Vec2): boolean { + return point.x >= this.x - this.hw && point.x <= this.x + this.hw + && point.y >= this.y - this.hh && point.y <= this.y + this.hh + } + + /** + * A simple boolean check of whether this AABB contains a point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point + */ + intersectPoint(point: Vec2): boolean { + let dx = point.x - this.x; + let px = this.hw - Math.abs(dx); + + if(px <= 0){ + return false; + } + + let dy = point.y - this.y; + let py = this.hh - Math.abs(dy); + + if(py <= 0){ + return false; + } + + return true; + } + + /** + * A boolean check of whether this AABB contains a point with soft left and top boundaries. + * In other words, if the top left is (0, 0), the point (0, 0) is not in the AABB + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point + */ + containsPointSoft(point: Vec2): boolean { + return point.x > this.x - this.hw && point.x <= this.x + this.hw + && point.y > this.y - this.hh && point.y <= this.y + this.hh + } + + + /** + * Returns the data from the intersection of this AABB with a line segment from a point in a direction + * @param point The point that the line segment starts from + * @param delta The direction and distance of the segment + * @param padding Pads the AABB to make it wider for the intersection test + * @returns The Hit object representing the intersection, or null if there was no intersection + */ + intersectSegment(point: Vec2, delta: Vec2, padding?: Vec2): Hit { + let paddingX = padding ? padding.x : 0; + let paddingY = padding ? padding.y : 0; + + let scaleX = 1/delta.x; + let scaleY = 1/delta.y; + + let signX = MathUtils.sign(scaleX); + let signY = MathUtils.sign(scaleY); + + let tnearx = scaleX*(this.x - signX*(this.hw + paddingX) - point.x); + let tneary = scaleY*(this.y - signY*(this.hh + paddingY) - point.y); + let tfarx = scaleX*(this.x + signX*(this.hw + paddingX) - point.x); + let tfary = scaleY*(this.y + signY*(this.hh + paddingY) - point.y); + + if(tnearx > tfary || tneary > tfarx){ + // We aren't colliding - we clear one axis before intersecting another + return null; + } + + let tnear = Math.max(tnearx, tneary); + + // Double check for NaNs + if(tnearx !== tnearx){ + tnear = tneary; + } else if (tneary !== tneary){ + tnear = tnearx; + } + + let tfar = Math.min(tfarx, tfary); + + if(tnear === -Infinity){ + return null; + } + + if(tnear >= 1 || tfar <= 0){ + return null; + } + + // We are colliding + let hit = new Hit(); + hit.time = MathUtils.clamp01(tnear); + hit.nearTimes.x = tnearx; + hit.nearTimes.y = tneary; + + if(tnearx > tneary){ + // We hit on the left or right size + hit.normal.x = -signX; + hit.normal.y = 0; + } else if(Math.abs(tnearx - tneary) < 0.0001){ + // We hit on the corner + hit.normal.x = -signX; + hit.normal.y = -signY; + hit.normal.normalize(); + } else { + // We hit on the top or bottom + hit.normal.x = 0; + hit.normal.y = -signY; + } + + hit.delta.x = (1.0 - hit.time) * -delta.x; + hit.delta.y = (1.0 - hit.time) * -delta.y; + hit.pos.x = point.x + delta.x * hit.time; + hit.pos.y = point.y + delta.y * hit.time; + + return hit; + } + + // @override + overlaps(other: Shape): boolean { + if(other instanceof AABB){ + return this.overlapsAABB(other); + } + throw "Overlap not defined between these shapes." + } + + /** + * A simple boolean check of whether this AABB overlaps another + * @param other The other AABB to check against + * @returns True if this AABB overlaps the other, false otherwise + */ + protected overlapsAABB(other: AABB): boolean { + let dx = other.x - this.x; + let px = this.hw + other.hw - Math.abs(dx); + + if(px <= 0){ + return false; + } + + let dy = other.y - this.y; + let py = this.hh + other.hh - Math.abs(dy); + + if(py <= 0){ + return false; + } + + return true; + } + + /** + * Determines whether these AABBs are JUST touching - not overlapping. + * Vec2.x is -1 if the other is to the left, 1 if to the right. + * Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom. + * @param other The other AABB to check + * @returns The collision sides stored in a Vec2 if the AABBs are touching, null otherwise + */ + touchesAABB(other: AABB): Vec2 { + let dx = other.x - this.x; + let px = this.hw + other.hw - Math.abs(dx); + + let dy = other.y - this.y; + let py = this.hh + other.hh - Math.abs(dy); + + // If one axis is just touching and the other is overlapping, true + if((px === 0 && py >= 0) || (py === 0 && px >= 0)){ + let ret = new Vec2(); + + if(px === 0){ + ret.x = other.x < this.x ? -1 : 1; + } + + if(py === 0){ + ret.y = other.y < this.y ? -1 : 1; + } + + return ret; + } else { + return null; + } + } + + /** + * Determines whether these AABBs are JUST touching - not overlapping. + * Also, if they are only touching corners, they are considered not touching. + * Vec2.x is -1 if the other is to the left, 1 if to the right. + * Likewise, Vec2.y is -1 if the other is on top, 1 if on bottom. + * @param other The other AABB to check + * @returns The side of the touch, stored as a Vec2, or null if there is no touch + */ + touchesAABBWithoutCorners(other: AABB): Vec2 { + let dx = other.x - this.x; + let px = this.hw + other.hw - Math.abs(dx); + + let dy = other.y - this.y; + let py = this.hh + other.hh - Math.abs(dy); + + // If one axis is touching, and the other is strictly overlapping + if((px === 0 && py > 0) || (py === 0 && px > 0)){ + let ret = new Vec2(); + + if(px === 0){ + ret.x = other.x < this.x ? -1 : 1; + } else { + ret.y = other.y < this.y ? -1 : 1; + } + + return ret; + + } else { + return null; + } + } + + /** + * Calculates the area of the overlap between this AABB and another + * @param other The other AABB + * @returns The area of the overlap between the AABBs + */ + overlapArea(other: AABB): number { + let leftx = Math.max(this.x - this.hw, other.x - other.hw); + let rightx = Math.min(this.x + this.hw, other.x + other.hw); + let dx = rightx - leftx; + + let lefty = Math.max(this.y - this.hh, other.y - other.hh); + let righty = Math.min(this.y + this.hh, other.y + other.hh); + let dy = righty - lefty; + + if(dx < 0 || dy < 0) return 0; + + return dx*dy; + } + + /** + * Moves and resizes this rect from its current position to the position specified + * @param velocity The movement of the rect from its position + * @param fromPosition A position specified to be the starting point of sweeping + * @param halfSize The halfSize of the sweeping rect + */ + sweep(velocity: Vec2, fromPosition?: Vec2, halfSize?: Vec2): void { + if(!fromPosition){ + fromPosition = this.center; + } + + if(!halfSize){ + halfSize = this.halfSize; + } + + let centerX = fromPosition.x + velocity.x/2; + let centerY = fromPosition.y + velocity.y/2; + + let minX = Math.min(fromPosition.x - halfSize.x, fromPosition.x + velocity.x - halfSize.x); + let minY = Math.min(fromPosition.y - halfSize.y, fromPosition.y + velocity.y - halfSize.y); + + this.center.set(centerX, centerY); + this.halfSize.set(centerX - minX, centerY - minY); + } + + // @override + clone(): AABB { + return new AABB(this.center.clone(), this.halfSize.clone()); + } + + /** + * Converts this AABB to a string format + * @returns (center: (x, y), halfSize: (x, y)) + */ + toString(): string { + return "(center: " + this.center.toString() + ", half-size: " + this.halfSize.toString() + ")" + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Shapes/Circle.ts b/hw3/src/Wolfie2D/DataTypes/Shapes/Circle.ts new file mode 100644 index 0000000..004ab79 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Shapes/Circle.ts @@ -0,0 +1,76 @@ +import Vec2 from "../Vec2"; +import AABB from "./AABB"; +import Shape from "./Shape"; + +/** + * A Circle + */ +export default class Circle extends Shape { + private _center: Vec2; + radius: number; + + /** + * Creates a new Circle + * @param center The center of the circle + * @param radius The radius of the circle + */ + constructor(center: Vec2, radius: number) { + super(); + this._center = center ? center : new Vec2(0, 0); + this.radius = radius ? radius : 0; + } + + get center(): Vec2 { + return this._center; + } + + set center(center: Vec2) { + this._center = center; + } + + get halfSize(): Vec2 { + return new Vec2(this.radius, this.radius); + } + + get r(): number { + return this.radius; + } + + set r(radius: number) { + this.radius = radius; + } + + // @override + /** + * A simple boolean check of whether this AABB contains a point + * @param point The point to check + * @returns A boolean representing whether this AABB contains the specified point + */ + containsPoint(point: Vec2): boolean { + return this.center.distanceSqTo(point) <= this.radius*this.radius; + } + + // @override + getBoundingRect(): AABB { + return new AABB(this._center.clone(), new Vec2(this.radius, this.radius)); + } + + // @override + getBoundingCircle(): Circle { + return this.clone(); + } + + // @override + overlaps(other: Shape): boolean { + throw new Error("Method not implemented."); + } + + // @override + clone(): Circle { + return new Circle(this._center.clone(), this.radius); + } + + toString(): string { + return "(center: " + this.center.toString() + ", radius: " + this.radius + ")"; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Shapes/Shape.ts b/hw3/src/Wolfie2D/DataTypes/Shapes/Shape.ts new file mode 100644 index 0000000..04113c9 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Shapes/Shape.ts @@ -0,0 +1,169 @@ +import Vec2 from "../Vec2"; +import AABB from "./AABB"; +import Circle from "./Circle"; + +/** + * An abstract Shape class that acts as an interface for better interactions with subclasses. + */ +export default abstract class Shape { + abstract get center(): Vec2; + + abstract set center(center: Vec2); + + abstract get halfSize(): Vec2; + + get x(): number { + return this.center.x; + } + + get y(): number { + return this.center.y; + } + + get hw(): number { + return this.halfSize.x; + } + + get hh(): number { + return this.halfSize.y; + } + + get top(): number { + return this.y - this.hh; + } + + get bottom(): number { + return this.y + this.hh; + } + + get left(): number { + return this.x - this.hw; + } + + get right(): number { + return this.x + this.hw; + } + + /** + * Gets a bounding rectangle for this shape. Warning - may be the same as this Shape. + * For instance, the bounding circle of an AABB is itself. Use clone() if you need a new shape. + * @returns An AABB that bounds this shape + */ + abstract getBoundingRect(): AABB; + + /** + * Gets a bounding circle for this shape. Warning - may be the same as this Shape. + * For instance, the bounding circle of a Circle is itself. Use clone() if you need a new shape. + * @returns A Circle that bounds this shape + */ + abstract getBoundingCircle(): Circle; + + /** + * Returns a copy of this Shape + * @returns A new copy of this shape + */ + abstract clone(): Shape; + + /** + * Checks if this shape overlaps another + * @param other The other shape to check against + * @returns a boolean that represents whether this Shape overlaps the other one + */ + abstract overlaps(other: Shape): boolean; + + /** + * A simple boolean check of whether this Shape contains a point + * @param point The point to check + * @returns A boolean representing whether this Shape contains the specified point + */ + abstract containsPoint(point: Vec2): boolean; + + static getTimeOfCollision(A: Shape, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] { + if(A instanceof AABB && B instanceof AABB){ + return Shape.getTimeOfCollision_AABB_AABB(A, velA, B, velB); + } + } + + private static getTimeOfCollision_AABB_AABB(A: AABB, velA: Vec2, B: Shape, velB: Vec2): [Vec2, Vec2, boolean, boolean] { + let posSmaller = A.center; + let posLarger = B.center; + + let sizeSmaller = A.halfSize; + let sizeLarger = B.halfSize; + + let firstContact = new Vec2(0, 0); + let lastContact = new Vec2(0, 0); + + let collidingX = false; + let collidingY = false; + + // Sort by position + if(posLarger.x < posSmaller.x){ + // Swap, because smaller is further right than larger + let temp: Vec2; + temp = sizeSmaller; + sizeSmaller = sizeLarger; + sizeLarger = temp; + + temp = posSmaller; + posSmaller = posLarger; + posLarger = temp; + + temp = velA; + velA = velB; + velB = temp; + } + + // A is left, B is right + firstContact.x = Infinity; + lastContact.x = Infinity; + + if (posLarger.x - sizeLarger.x >= posSmaller.x + sizeSmaller.x){ + // If we aren't currently colliding + let relVel = velA.x - velB.x; + + if(relVel > 0){ + // If they are moving towards each other + firstContact.x = ((posLarger.x - sizeLarger.x) - (posSmaller.x + sizeSmaller.x))/(relVel); + lastContact.x = ((posLarger.x + sizeLarger.x) - (posSmaller.x - sizeSmaller.x))/(relVel); + } + } else { + collidingX = true; + } + + if(posLarger.y < posSmaller.y){ + // Swap, because smaller is further up than larger + let temp: Vec2; + temp = sizeSmaller; + sizeSmaller = sizeLarger; + sizeLarger = temp; + + temp = posSmaller; + posSmaller = posLarger; + posLarger = temp; + + temp = velA; + velA = velB; + velB = temp; + } + + // A is top, B is bottom + firstContact.y = Infinity; + lastContact.y = Infinity; + + if (posLarger.y - sizeLarger.y >= posSmaller.y + sizeSmaller.y){ + // If we aren't currently colliding + let relVel = velA.y - velB.y; + + if(relVel > 0){ + // If they are moving towards each other + firstContact.y = ((posLarger.y - sizeLarger.y) - (posSmaller.y + sizeSmaller.y))/(relVel); + lastContact.y = ((posLarger.y + sizeLarger.y) - (posSmaller.y - sizeSmaller.y))/(relVel); + } + } else { + collidingY = true; + } + + return [firstContact, lastContact, collidingX, collidingY]; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Spritesheet.ts b/hw3/src/Wolfie2D/DataTypes/Spritesheet.ts new file mode 100644 index 0000000..d1f845c --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Spritesheet.ts @@ -0,0 +1,22 @@ +import { AnimationData } from "../Rendering/Animations/AnimationTypes"; + +/** A class representing data contained in a spritesheet. + * Spritesheets are the images associated with sprites, and contain images indexed in a grid, which + * correspond to animations. + */ +export default class Spritesheet { + /** The name of the spritesheet */ + name: string; + /** The image key of the spritesheet */ + spriteSheetImage: string; + /** The width of the sprite */ + spriteWidth: number; + /** The height of the sprite */ + spriteHeight: number; + /** The number of columns in the spritesheet */ + columns: number; + /** The number of rows in the spritesheet */ + rows: number; + /** An array of the animations associated with this spritesheet */ + animations: Array; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Stack.ts b/hw3/src/Wolfie2D/DataTypes/Stack.ts new file mode 100644 index 0000000..d45d5b5 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Stack.ts @@ -0,0 +1,108 @@ +import Collection from "./Collection"; + +/** + * A LIFO stack with items of type T + */ +export default class Stack implements Collection { + /** The maximum number of elements in the Stack */ + private MAX_ELEMENTS: number; + + /** The internal representation of the stack */ + private stack: Array; + + /** The head of the stack */ + private head: number; + + /** + * Constructs a new stack + * @param maxElements The maximum size of the stack + */ + constructor(maxElements: number = 100){ + this.MAX_ELEMENTS = maxElements; + this.stack = new Array(this.MAX_ELEMENTS); + this.head = -1; + } + + /** + * Adds an item to the top of the stack + * @param item The new item to add to the stack + */ + push(item: T): void { + if(this.head + 1 === this.MAX_ELEMENTS){ + throw "Stack full - cannot add element"; + } + this.head += 1; + this.stack[this.head] = item; + } + + /** + * Removes an item from the top of the stack + * @returns The item at the top of the stack + */ + pop(): T { + if(this.head === -1){ + throw "Stack empty - cannot remove element"; + } + this.head -= 1; + return this.stack[this.head + 1]; + } + + /** + * Returns the element currently at the top of the stack + * @returns The item at the top of the stack + */ + peek(): T { + if(this.head === -1){ + throw "Stack empty - cannot get element"; + } + return this.stack[this.head]; + } + + /** Returns true if this stack is empty + * @returns A boolean that represents whether or not the stack is empty + */ + isEmpty(): boolean { + return this.head === -1; + } + + // @implemented + clear(): void { + this.forEach((item, index) => delete this.stack[index]); + this.head = -1; + } + + /** + * Returns the number of items currently in the stack + * @returns The number of items in the stack + */ + size(): number { + return this.head + 1; + } + + // @implemented + forEach(func: (item: T, index?: number) => void): void{ + let i = 0; + while(i <= this.head){ + func(this.stack[i], i); + i += 1; + } + } + + /** + * Converts this stack into a string format + * @returns A string representing this stack + */ + toString(): string { + let retval = ""; + + this.forEach( (item, index) => { + let str = item.toString() + if(index !== 0){ + str += " -> " + } + retval = str + retval; + }); + + return "Top -> " + retval; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/State/State.ts b/hw3/src/Wolfie2D/DataTypes/State/State.ts new file mode 100644 index 0000000..469ce1d --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/State/State.ts @@ -0,0 +1,54 @@ +import Emitter from "../../Events/Emitter"; +import GameEvent from "../../Events/GameEvent"; +import Updateable from "../Interfaces/Updateable"; +import StateMachine from "./StateMachine"; + +/** + * An abstract implementation of a state for a @reference[StateMachine]. + * This class should be extended to allow for custom state behaviors. + */ +export default abstract class State implements Updateable { + /** The StateMachine that uses this State */ + protected parent: StateMachine; + + /** An event emitter */ + protected emitter: Emitter; + + /** + * Constructs a new State + * @param parent The parent StateMachine of this state + */ + constructor(parent: StateMachine) { + this.parent = parent; + this.emitter = new Emitter(); + } + + /** + * A method that is called when this state is entered. Use this to initialize any variables before updates occur. + * @param options Information to pass to this state + */ + abstract onEnter(options: Record): void; + + /** + * A lifecycle method that handles an input event, such as taking damage. + * @param event The GameEvent to process + */ + abstract handleInput(event: GameEvent): void; + + // @implemented + abstract update(deltaT: number): void; + + /** + * Tells the state machine that this state has ended, and makes it transition to the new state specified + * @param stateName The name of the state to transition to + */ + protected finished(stateName: string): void { + this.parent.changeState(stateName); + } + + /** + * A lifecycle method is called when the state is ending. + * @returns info to pass to the next state + */ + abstract onExit(): Record; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/State/StateMachine.ts b/hw3/src/Wolfie2D/DataTypes/State/StateMachine.ts new file mode 100644 index 0000000..a6382e7 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/State/StateMachine.ts @@ -0,0 +1,137 @@ +import Stack from "../Stack"; +import State from "./State"; +import Map from "../Map"; +import GameEvent from "../../Events/GameEvent"; +import Receiver from "../../Events/Receiver"; +import Emitter from "../../Events/Emitter"; +import Updateable from "../Interfaces/Updateable"; + +/** + * An implementation of a Push Down Automata State machine. States can also be hierarchical + * for more flexibility, as described in @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/state.html). + */ +export default class StateMachine implements Updateable { + /** A stack of the current states */ + protected stack: Stack; + /** A mape of state keys to actual state instances */ + protected stateMap: Map; + /** The current state */ + protected currentState: State; + /** An event receiver */ + protected receiver: Receiver; + /** An event emitter */ + protected emitter: Emitter; + /** A boolean representing whether or not this StateMachine is currently active */ + protected active: boolean; + /** A boolean representing whether or not this StateMachine should emit an event on state change */ + protected emitEventOnStateChange: boolean; + /** The name of the event to be emitted on state change */ + protected stateChangeEventName: string; + + /** + * Creates a new StateMachine + */ + constructor(){ + this.stack = new Stack(); + this.stateMap = new Map(); + this.receiver = new Receiver(); + this.emitter = new Emitter(); + this.emitEventOnStateChange = false; + } + + /** + * Sets the activity state of this state machine + * @param flag True if you want to set this machine running, false otherwise + */ + setActive(flag: boolean): void { + this.active = flag; + } + + /** + * Makes this state machine emit an event any time its state changes + * @param stateChangeEventName The name of the event to emit + */ + setEmitEventOnStateChange(stateChangeEventName: string): void { + this.emitEventOnStateChange = true; + this.stateChangeEventName = stateChangeEventName; + } + + /** + * Stops this state machine from emitting events on state change. + */ + cancelEmitEventOnStateChange(): void { + this.emitEventOnStateChange = false; + } + + /** + * Initializes this state machine with an initial state and sets it running + * @param initialState The name of initial state of the state machine + */ + initialize(initialState: string, options: Record = {}): void { + this.stack.push(this.stateMap.get(initialState)); + this.currentState = this.stack.peek(); + this.currentState.onEnter(options); + this.setActive(true); + } + + /** + * Adds a state to this state machine + * @param stateName The name of the state to add + * @param state The state to add + */ + addState(stateName: string, state: State): void { + this.stateMap.add(stateName, state); + } + + /** + * Changes the state of this state machine to the provided string + * @param state The string name of the state to change to + */ + changeState(state: string): void { + // Exit the current state + let options = this.currentState.onExit(); + + // Make sure the correct state is at the top of the stack + if(state === "previous"){ + // Pop the current state off the stack + this.stack.pop(); + } else { + // Retrieve the new state from the statemap and put it at the top of the stack + this.stack.pop(); + this.stack.push(this.stateMap.get(state)); + } + + // Retreive the new state from the stack + this.currentState = this.stack.peek(); + + // Emit an event if turned on + if(this.emitEventOnStateChange){ + this.emitter.fireEvent(this.stateChangeEventName, {state: this.currentState}); + } + + // Enter the new state + this.currentState.onEnter(options); + } + + /** + * Handles input. This happens at the very beginning of this state machine's update cycle. + * @param event The game event to process + */ + handleEvent(event: GameEvent): void { + if(this.active){ + this.currentState.handleInput(event); + } + } + + // @implemented + update(deltaT: number): void { + // Distribute events + while(this.receiver.hasNextEvent()){ + let event = this.receiver.getNextEvent(); + this.handleEvent(event); + } + + // Delegate the update to the current state + this.currentState.update(deltaT); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Tilesets/TiledData.ts b/hw3/src/Wolfie2D/DataTypes/Tilesets/TiledData.ts new file mode 100644 index 0000000..25146db --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Tilesets/TiledData.ts @@ -0,0 +1,78 @@ +// @ignorePage +/** + * a representation of Tiled's tilemap data + */ +export class TiledTilemapData { + height: number; + width: number; + tileheight: number; + tilewidth: number; + orientation: string; + layers: Array; + tilesets: Array; +} + +/** + * A representation of a custom layer property in a Tiled tilemap + */ +export class TiledLayerProperty { + name: string; + type: string; + value: any; +} + +/** + * A representation of a tileset in a Tiled tilemap + */ +export class TiledTilesetData { + columns: number; + tilewidth: number; + tileheight: number; + tilecount: number; + firstgid: number; + imageheight: number; + imagewidth: number; + margin: number; + spacing: number; + name: string; + image: string; + tiles: Array +} + +/** + * A representation of a layer in a Tiled tilemap + */ +export class TiledLayerData { + data: number[]; + x: number; + y: number; + width: number; + height: number; + name: string; + opacity: number; + visible: boolean; + properties: TiledLayerProperty[]; + type: string; + objects: Array; +} + +export class TiledObject { + gid: number; + height: number; + width: number; + id: number; + name: string;; + properties: Array; + rotation: number; + type: string; + visible: boolean; + x: number; + y: number; +} + +export class TiledCollectionTile { + id: number; + image: string; + imageheight: number; + imagewidth: number; +} diff --git a/hw3/src/Wolfie2D/DataTypes/Tilesets/Tileset.ts b/hw3/src/Wolfie2D/DataTypes/Tilesets/Tileset.ts new file mode 100644 index 0000000..e3d6419 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Tilesets/Tileset.ts @@ -0,0 +1,146 @@ +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Vec2 from "../Vec2"; +import { TiledTilesetData } from "./TiledData"; + +/** + * The data representation of a Tileset for the game engine. This represents one image, + * with a startIndex if required (as it is with Tiled using two images in one tilset). + */ +export default class Tileset { + /** The key of the image used by this tileset */ + protected imageKey: string; + /** The size of the tileset image */ + protected imageSize: Vec2; + /** The index of 0th image of this tileset */ + protected startIndex: number; + /** The index of the last image of this tilset */ + protected endIndex: number; + /** The size of the tiles in this tileset */ + protected tileSize: Vec2; + /** The number of rows in this tileset */ + protected numRows: number; + /** The number of columns in this tileset */ + protected numCols: number; + + // TODO: Change this to be more general and work with other tileset formats + constructor(tilesetData: TiledTilesetData){ + // Defer handling of the data to a helper class + this.initFromTiledData(tilesetData); + } + + /** + * Initialize the tileset from the data from a Tiled json file + * @param tiledData The parsed object from a Tiled json file + */ + initFromTiledData(tiledData: TiledTilesetData): void { + this.numRows = tiledData.tilecount/tiledData.columns; + this.numCols = tiledData.columns; + this.startIndex = tiledData.firstgid; + this.endIndex = this.startIndex + tiledData.tilecount - 1; + this.tileSize = new Vec2(tiledData.tilewidth, tiledData.tilewidth); + this.imageKey = tiledData.image; + this.imageSize = new Vec2(tiledData.imagewidth, tiledData.imageheight); + } + + /** + * Gets the image key associated with this tilemap + * @returns The image key of this tilemap + */ + getImageKey(): string { + return this.imageKey; + } + + /** + * Returns a Vec2 containing the left and top offset from the image origin for this tile. + * @param tileIndex The index of the tile from startIndex to endIndex of this tileset + * @returns A Vec2 containing the offset for the specified tile. + */ + getImageOffsetForTile(tileIndex: number): Vec2 { + // Get the true index + let index = tileIndex - this.startIndex; + let row = Math.floor(index / this.numCols); + let col = index % this.numCols; + let width = this.tileSize.x; + let height = this.tileSize.y; + + // Calculate the position to start a crop in the tileset image + let left = col * width; + let top = row * height; + + return new Vec2(left, top); + } + + /** + * Gets the start index + * @returns The start index + */ + getStartIndex(): number { + return this.startIndex; + } + + /** + * Gets the tile set + * @returns A Vec2 containing the tile size + */ + getTileSize(): Vec2 { + return this.tileSize; + } + + /** + * Gets the number of rows in the tileset + * @returns The number of rows + */ + getNumRows(): number { + return this.numRows; + } + + /** + * Gets the number of columns in the tilset + * @returns The number of columns + */ + getNumCols(): number { + return this.numCols; + } + + getTileCount(): number { + return this.endIndex - this.startIndex + 1; + } + + /** + * Checks whether or not this tilset contains the specified tile index. This is used for rendering. + * @param tileIndex The index of the tile to check + * @returns A boolean representing whether or not this tilset uses the specified index + */ + hasTile(tileIndex: number): boolean { + return tileIndex >= this.startIndex && tileIndex <= this.endIndex; + } + + /** + * Render a singular tile with index tileIndex from the tileset located at position dataIndex + * @param ctx The rendering context + * @param tileIndex The value of the tile to render + * @param dataIndex The index of the tile in the data array + * @param worldSize The size of the world + * @param origin The viewport origin in the current layer + * @param scale The scale of the tilemap + */ + renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, maxCols: number, origin: Vec2, scale: Vec2, zoom: number): void { + let image = ResourceManager.getInstance().getImage(this.imageKey); + + // Get the true index + let index = tileIndex - this.startIndex; + let row = Math.floor(index / this.numCols); + let col = index % this.numCols; + let width = this.tileSize.x; + let height = this.tileSize.y; + + // Calculate the position to start a crop in the tileset image + let left = col * width; + let top = row * height; + + // Calculate the position in the world to render the tile + let x = Math.floor((dataIndex % maxCols) * width * scale.x); + let y = Math.floor(Math.floor(dataIndex / maxCols) * height * scale.y); + ctx.drawImage(image, left, top, width, height, Math.floor((x - origin.x)*zoom), Math.floor((y - origin.y)*zoom), Math.ceil(width * scale.x * zoom), Math.ceil(height * scale.y * zoom)); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/DataTypes/Vec2.ts b/hw3/src/Wolfie2D/DataTypes/Vec2.ts new file mode 100644 index 0000000..1764525 --- /dev/null +++ b/hw3/src/Wolfie2D/DataTypes/Vec2.ts @@ -0,0 +1,432 @@ +import MathUtils from "../Utils/MathUtils"; + +/** + * A two-dimensional vector (x, y) + */ +export default class Vec2 { + + // Store x and y in an array + /** The array that stores the actual vector values x and y */ + private vec: Float32Array; + + /** + * When this vector changes its value, do something + */ + private onChange: Function = () => {}; + + /** + * Creates a new Vec2 + * @param x The x value of the vector + * @param y The y value of the vector + */ + constructor(x: number = 0, y: number = 0) { + this.vec = new Float32Array(2); + this.vec[0] = x; + this.vec[1] = y; + } + + // Expose x and y with getters and setters + get x() { + return this.vec[0]; + } + + set x(x: number) { + this.vec[0] = x; + + if(this.onChange){ + this.onChange(); + } + } + + get y() { + return this.vec[1]; + } + + set y(y: number) { + this.vec[1] = y; + + if(this.onChange){ + this.onChange(); + } + } + + static get ZERO() { + return new Vec2(0, 0); + } + + static readonly ZERO_STATIC = new Vec2(0, 0); + + static get INF() { + return new Vec2(Infinity, Infinity); + } + + static get UP() { + return new Vec2(0, -1); + } + + static get DOWN() { + return new Vec2(0, 1); + } + + static get LEFT() { + return new Vec2(-1, 0); + } + + static get RIGHT() { + return new Vec2(1, 0); + } + + /** + * The squared magnitude of the vector. This tends to be faster, so use it in situations where taking the + * square root doesn't matter, like for comparing distances. + * @returns The squared magnitude of the vector + */ + magSq(): number { + return this.x*this.x + this.y*this.y; + } + + /** + * The magnitude of the vector. + * @returns The magnitude of the vector. + */ + mag(): number { + return Math.sqrt(this.magSq()); + } + + /** + * Divdes x and y by the magnitude to obtain the unit vector in the direction of this vector. + * @returns This vector as a unit vector. + */ + normalize(): Vec2 { + if(this.x === 0 && this.y === 0) return this; + let mag = this.mag(); + this.x /= mag; + this.y /= mag; + return this; + } + + /** + * Works like normalize(), but returns a new Vec2 + * @returns A new vector that is the unit vector for this one + */ + normalized(): Vec2 { + if(this.isZero()){ + return this; + } + + let mag = this.mag(); + return new Vec2(this.x/mag, this.y/mag); + } + + /** + * Sets the x and y elements of this vector to zero. + * @returns This vector, with x and y set to zero. + */ + zero(): Vec2 { + return this.set(0, 0); + } + + /** + * Sets the vector's x and y based on the angle provided. Goes counter clockwise. + * @param angle The angle in radians + * @param radius The magnitude of the vector at the specified angle + * @returns This vector. + */ + setToAngle(angle: number, radius: number = 1): Vec2 { + this.x = MathUtils.floorToPlace(Math.cos(angle)*radius, 5); + this.y = MathUtils.floorToPlace(-Math.sin(angle)*radius, 5); + return this; + } + + /** + * Returns a vector that point from this vector to another one + * @param other The vector to point to + * @returns A new Vec2 that points from this vector to the one provided + */ + vecTo(other: Vec2): Vec2 { + return new Vec2(other.x - this.x, other.y - this.y); + } + + /** + * Returns a vector containing the direction from this vector to another + * @param other The vector to point to + * @returns A new Vec2 that points from this vector to the one provided. This new Vec2 will be a unit vector. + */ + dirTo(other: Vec2): Vec2 { + return this.vecTo(other).normalize(); + } + + /** + * Keeps the vector's direction, but sets its magnitude to be the provided magnitude + * @param magnitude The magnitude the vector should be + * @returns This vector with its magnitude set to the new magnitude + */ + scaleTo(magnitude: number): Vec2 { + return this.normalize().scale(magnitude); + } + + /** + * Scales x and y by the number provided, or if two number are provided, scales them individually. + * @param factor The scaling factor for the vector, or for only the x-component if yFactor is provided + * @param yFactor The scaling factor for the y-component of the vector + * @returns This vector after scaling + */ + scale(factor: number, yFactor: number = null): Vec2 { + if(yFactor !== null){ + this.x *= factor; + this.y *= yFactor; + return this; + } + this.x *= factor; + this.y *= factor; + return this; + } + + /** + * Returns a scaled version of this vector without modifying it. + * @param factor The scaling factor for the vector, or for only the x-component if yFactor is provided + * @param yFactor The scaling factor for the y-component of the vector + * @returns A new vector that has the values of this vector after scaling + */ + scaled(factor: number, yFactor: number = null): Vec2 { + return this.clone().scale(factor, yFactor); + } + + /** + * Rotates the vector counter-clockwise by the angle amount specified + * @param angle The angle to rotate by in radians + * @returns This vector after rotation. + */ + rotateCCW(angle: number): Vec2 { + let cs = Math.cos(angle); + let sn = Math.sin(angle); + let tempX = this.x*cs - this.y*sn; + let tempY = this.x*sn + this.y*cs; + this.x = tempX; + this.y = tempY; + return this; + } + + /** + * Sets the vectors coordinates to be the ones provided + * @param x The new x value for this vector + * @param y The new y value for this vector + * @returns This vector + */ + set(x: number, y: number): Vec2 { + this.x = x; + this.y = y; + return this; + } + + /** + * Copies the values of the other Vec2 into this one. + * @param other The Vec2 to copy + * @returns This vector with its values set to the vector provided + */ + copy(other: Vec2): Vec2 { + return this.set(other.x, other.y); + } + + /** + * Adds this vector the another vector + * @param other The Vec2 to add to this one + * @returns This vector after adding the one provided + */ + add(other: Vec2): Vec2 { + this.x += other.x; + this.y += other.y; + return this; + } + + /** + * Increments the fields of this vector. Both are incremented with a, if only a is provided. + * @param a The first number to increment by + * @param b The second number to increment by + * @returnss This vector after incrementing + */ + inc(a: number, b?: number): Vec2 { + if(b === undefined){ + this.x += a; + this.y += a; + } else { + this.x += a; + this.y += b; + } + return this; + } + + /** + * Subtracts another vector from this vector + * @param other The Vec2 to subtract from this one + * @returns This vector after subtracting the one provided + */ + sub(other: Vec2): Vec2 { + this.x -= other.x; + this.y -= other.y; + return this; + } + + /** + * Multiplies this vector with another vector element-wise. In other words, this.x *= other.x and this.y *= other.y + * @param other The Vec2 to multiply this one by + * @returns This vector after multiplying its components by this one + */ + mult(other: Vec2): Vec2 { + this.x *= other.x; + this.y *= other.y; + return this; + } + + /** + * Divides this vector with another vector element-wise. In other words, this.x /= other.x and this.y /= other.y + * @param other The vector to divide this one by + * @returns This vector after division + */ + div(other: Vec2): Vec2 { + if(other.x === 0 || other.y === 0) throw "Divide by zero error"; + this.x /= other.x; + this.y /= other.y; + return this; + } + + /** + * Does an element wise remainder operation on this vector. this.x %= other.x and this.y %= other.y + * @param other The other vector + * @returns this vector + */ + remainder(other: Vec2): Vec2 { + this.x = this.x % other.x; + this.y = this.y % other.y; + return this; + } + + /** + * Returns the squared distance between this vector and another vector + * @param other The vector to compute distance squared to + * @returns The squared distance between this vector and the one provided + */ + distanceSqTo(other: Vec2): number { + return (this.x - other.x)*(this.x - other.x) + (this.y - other.y)*(this.y - other.y); + } + + /** + * Returns the distance between this vector and another vector + * @param other The vector to compute distance to + * @returns The distance between this vector and the one provided + */ + distanceTo(other: Vec2): number { + return Math.sqrt(this.distanceSqTo(other)); + } + + /** + * Returns the dot product of this vector and another + * @param other The vector to compute the dot product with + * @returns The dot product of this vector and the one provided. + */ + dot(other: Vec2): number { + return this.x*other.x + this.y*other.y; + } + + /** + * Returns the angle counter-clockwise in radians from this vector to another vector + * @param other The vector to compute the angle to + * @returns The angle, rotating CCW, from this vector to the other vector + */ + angleToCCW(other: Vec2): number { + let dot = this.dot(other); + let det = this.x*other.y - this.y*other.x; + let angle = -Math.atan2(det, dot); + + if(angle < 0){ + angle += 2*Math.PI; + } + + return angle; + } + + /** + * Returns a string representation of this vector rounded to 1 decimal point + * @returns This vector as a string + */ + toString(): string { + return this.toFixed(); + } + + /** + * Returns a string representation of this vector rounded to the specified number of decimal points + * @param numDecimalPoints The number of decimal points to create a string to + * @returns This vector as a string + */ + toFixed(numDecimalPoints: number = 1): string { + return "(" + this.x.toFixed(numDecimalPoints) + ", " + this.y.toFixed(numDecimalPoints) + ")"; + } + + /** + * Returns a new vector with the same coordinates as this one. + * @returns A new Vec2 with the same values as this one + */ + clone(): Vec2 { + return new Vec2(this.x, this.y); + } + + /** + * Returns true if this vector and other have the EXACT same x and y (not assured to be safe for floats) + * @param other The vector to check against + * @returns A boolean representing the equality of the two vectors + */ + strictEquals(other: Vec2): boolean { + return this.x === other.x && this.y === other.y; + } + + /** + * Returns true if this vector and other have the same x and y + * @param other The vector to check against + * @returns A boolean representing the equality of the two vectors + */ + equals(other: Vec2): boolean { + let xEq = Math.abs(this.x - other.x) < 0.0000001; + let yEq = Math.abs(this.y - other.y) < 0.0000001; + + return xEq && yEq; + } + + /** + * Returns true if this vector is the zero vector exactly (not assured to be safe for floats). + * @returns A boolean representing the equality of this vector and the zero vector + */ + strictIsZero(): boolean { + return this.x === 0 && this.y === 0; + } + + /** + * Returns true if this x and y for this vector are both zero. + * @returns A boolean representing the equality of this vector and the zero vector + */ + isZero(): boolean { + return Math.abs(this.x) < 0.0000001 && Math.abs(this.y) < 0.0000001; + } + + /** + * Sets the function that is called whenever this vector is changed. + * @param f The function to be called + */ + setOnChange(f: Function): void { + this.onChange = f; + } + + toArray(): Float32Array { + return this.vec; + } + + /** + * Performs linear interpolation between two vectors + * @param a The first vector + * @param b The second vector + * @param t The time of the lerp, with 0 being vector A, and 1 being vector B + * @returns A new Vec2 representing the lerp between vector a and b. + */ + static lerp(a: Vec2, b: Vec2, t: number): Vec2 { + return new Vec2(MathUtils.lerp(a.x, b.x, t), MathUtils.lerp(a.y, b.y, t)); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Debug/Debug.ts b/hw3/src/Wolfie2D/Debug/Debug.ts new file mode 100644 index 0000000..22cfc8b --- /dev/null +++ b/hw3/src/Wolfie2D/Debug/Debug.ts @@ -0,0 +1,197 @@ +import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import GameNode from "../Nodes/GameNode"; +import Color from "../Utils/Color"; + +/** + * A util class for rendering Debug messages to the canvas. + */ +export default class Debug { + + /** A map of log messages to display on the screen */ + private static logMessages: Map = new Map(); + + /** An array of game nodes to render debug info for */ + private static nodes: Array; + + /** The rendering context for any debug messages */ + private static debugRenderingContext: CanvasRenderingContext2D; + + /** The size of the debug canvas */ + private static debugCanvasSize: Vec2; + + /** The rendering color for text */ + private static defaultTextColor: Color = Color.WHITE; + + /** + * Add a message to display on the debug screen + * @param id A unique ID for this message + * @param messages The messages to print to the debug screen + */ + static log(id: string, ...messages: any): void { + // let message = ""; + // for(let i = 0; i < messages.length; i++){ + // message += messages[i].toString(); + // } + // Join all messages with spaces + let message = messages.map((m: any) => m.toString()).join(" "); + this.logMessages.add(id, message); + } + + /** + * Deletes a a key from the log and stops it from keeping up space on the screen + * @param id The id of the log item to clear + */ + static clearLogItem(id: string): void { + this.logMessages.delete(id); + } + + /** + * Sets the list of nodes to render with the debugger + * @param nodes The new list of nodes + */ + static setNodes(nodes: Array): void { + this.nodes = nodes; + } + + /** + * Draws a box at the specified position + * @param center The center of the box + * @param halfSize The dimensions of the box + * @param filled A boolean for whether or not the box is filled + * @param color The color of the box to draw + */ + static drawBox(center: Vec2, halfSize: Vec2, filled: boolean, color: Color): void { + let alpha = this.debugRenderingContext.globalAlpha; + this.debugRenderingContext.globalAlpha = color.a; + + if(filled){ + this.debugRenderingContext.fillStyle = color.toString(); + this.debugRenderingContext.fillRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); + } else { + let lineWidth = 2; + this.debugRenderingContext.lineWidth = lineWidth; + this.debugRenderingContext.strokeStyle = color.toString(); + this.debugRenderingContext.strokeRect(center.x - halfSize.x, center.y - halfSize.y, halfSize.x*2, halfSize.y*2); + } + + this.debugRenderingContext.globalAlpha = alpha; + } + + /** + * Draws a circle at the specified position + * @param center The center of the circle + * @param radius The dimensions of the box + * @param filled A boolean for whether or not the circle is filled + * @param color The color of the circle + */ + static drawCircle(center: Vec2, radius: number, filled: boolean, color: Color): void { + let alpha = this.debugRenderingContext.globalAlpha; + this.debugRenderingContext.globalAlpha = color.a; + + if(filled){ + this.debugRenderingContext.fillStyle = color.toString(); + this.debugRenderingContext.beginPath(); + this.debugRenderingContext.arc(center.x, center.y, radius, 0, 2 * Math.PI); + this.debugRenderingContext.closePath(); + this.debugRenderingContext.fill(); + } else { + let lineWidth = 2; + this.debugRenderingContext.lineWidth = lineWidth; + this.debugRenderingContext.strokeStyle = color.toString(); + this.debugRenderingContext.beginPath(); + this.debugRenderingContext.arc(center.x, center.y, radius, 0, 2 * Math.PI); + this.debugRenderingContext.closePath(); + this.debugRenderingContext.stroke(); + } + + this.debugRenderingContext.globalAlpha = alpha; + } + + /** + * Draws a ray at the specified position + * @param from The starting position of the ray + * @param to The ending position of the ray + * @param color The color of the ray + */ + static drawRay(from: Vec2, to: Vec2, color: Color): void { + this.debugRenderingContext.lineWidth = 2; + this.debugRenderingContext.strokeStyle = color.toString(); + + this.debugRenderingContext.beginPath(); + this.debugRenderingContext.moveTo(from.x, from.y); + this.debugRenderingContext.lineTo(to.x, to.y); + this.debugRenderingContext.closePath(); + this.debugRenderingContext.stroke(); + } + + /** + * Draws a point at the specified position + * @param pos The position of the point + * @param color The color of the point + */ + static drawPoint(pos: Vec2, color: Color): void { + let pointSize = 6; + this.debugRenderingContext.fillStyle = color.toString(); + this.debugRenderingContext.fillRect(pos.x - pointSize/2, pos.y - pointSize/2, pointSize, pointSize); + } + + /** + * Sets the default rendering color for text for the debugger + * @param color The color to render the text + */ + static setDefaultTextColor(color: Color): void { + this.defaultTextColor = color; + } + + /** + * Performs any necessary setup operations on the Debug canvas + * @param canvas The debug canvas + * @param width The desired width of the canvas + * @param height The desired height of the canvas + * @returns The rendering context extracted from the canvas + */ + static initializeDebugCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { + canvas.width = width; + canvas.height = height; + + this.debugCanvasSize = new Vec2(width, height); + + this.debugRenderingContext = canvas.getContext("2d"); + + return this.debugRenderingContext; + } + + /** Clears the debug canvas */ + static clearCanvas(): void { + this.debugRenderingContext.clearRect(0, 0, this.debugCanvasSize.x, this.debugCanvasSize.y); + } + + /** Renders the text and nodes sent to the Debug system */ + static render(): void { + this.renderText(); + this.renderNodes(); + } + + /** Renders the text sent to the Debug canvas */ + static renderText(): void { + let y = 20; + this.debugRenderingContext.font = "20px Arial"; + this.debugRenderingContext.fillStyle = this.defaultTextColor.toString(); + + // Draw all of the text + this.logMessages.forEach((key: string) => { + this.debugRenderingContext.fillText(this.logMessages.get(key), 10, y) + y += 30; + }); + } + + /** Renders the nodes registered with the debug canvas */ + static renderNodes(): void { + if(this.nodes){ + this.nodes.forEach(node => { + node.debugRender(); + }); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Debug/Stats.ts b/hw3/src/Wolfie2D/Debug/Stats.ts new file mode 100644 index 0000000..e9febd4 --- /dev/null +++ b/hw3/src/Wolfie2D/Debug/Stats.ts @@ -0,0 +1,244 @@ +import Color from "../Utils/Color"; + +// @ignorePage +export default class Stats extends Object { + // The fps of the game. + private static prevfps: Array; + private static readonly NUM_POINTS: number = 60; + private static ctx: CanvasRenderingContext2D; + private static CANVAS_WIDTH: number = 300; + private static CANVAS_HEIGHT: number = 300; + private static statsDiv: HTMLDivElement; + private static graphChoices: HTMLSelectElement; + + // Quadtree stats + private static prevClearTimes: Array; + private static SGClearTimes: Array; + private static avgSGClearTime: number; + + private static prevFillTimes: Array; + private static SGFillTimes: Array; + private static avgSGFillTime: number; + + private static prevUpdateTimes: Array; + private static SGUpdateTimes: Array; + private static avgSGUpdateTime: number; + + private static prevQueryTimes: Array; + private static SGQueryTimes: Array; + private static avgSGQueryTime: number; + + static initStats(): void { + let canvas = document.getElementById("stats-canvas"); + canvas.width = this.CANVAS_WIDTH; + canvas.height = this.CANVAS_HEIGHT; + this.ctx = canvas.getContext("2d"); + + this.statsDiv = document.getElementById("stats-display"); + + this.prevfps = new Array(); + + this.prevClearTimes = new Array(); + this.SGClearTimes = new Array(); + this.avgSGClearTime = 0; + + this.prevFillTimes = new Array(); + this.SGFillTimes = new Array(); + this.avgSGFillTime = 0; + + this.prevUpdateTimes = new Array(); + this.SGUpdateTimes = new Array(); + this.avgSGUpdateTime = 0; + + this.prevQueryTimes = new Array(); + this.SGQueryTimes = new Array(); + this.avgSGQueryTime = 0; + + let clearTime = document.createElement("span"); + clearTime.setAttribute("id", "sgclear"); + let fillTime = document.createElement("span"); + fillTime.setAttribute("id", "sgfill"); + let updateTime = document.createElement("span"); + updateTime.setAttribute("id", "sgupdate"); + let queryTime = document.createElement("span"); + queryTime.setAttribute("id", "sgquery"); + let br1 = document.createElement("br"); + let br2 = document.createElement("br"); + let br3 = document.createElement("br"); + + this.statsDiv.append(clearTime, br1, fillTime, br2, updateTime, br3, queryTime); + + this.graphChoices = document.getElementById("chart-option"); + let option1 = document.createElement("option"); + option1.value = "prevfps"; + option1.label = "FPS"; + let option2 = document.createElement("option"); + option2.value = "prevClearTimes"; + option2.label = "Clear Time"; + let option3 = document.createElement("option"); + option3.value = "prevFillTimes"; + option3.label = "Fill time"; + let option4 = document.createElement("option"); + option4.value = "prevUpdateTimes"; + option4.label = "Update time"; + let option5 = document.createElement("option"); + option5.value = "prevQueryTimes"; + option5.label = "Query Time"; + let optionAll = document.createElement("option"); + optionAll.value = "all"; + optionAll.label = "All"; + this.graphChoices.append(option1, option2, option3, option4, option5, optionAll); + } + + static updateFPS(fps: number): void { + this.prevfps.push(fps); + if(this.prevfps.length > Stats.NUM_POINTS){ + this.prevfps.shift(); + } + + if(this.SGClearTimes.length > 0){ + this.prevClearTimes.push(this.avgSGClearTime); + if(this.prevClearTimes.length > this.NUM_POINTS){ + this.prevClearTimes.shift(); + } + } + if(this.SGFillTimes.length > 0){ + this.prevFillTimes.push(this.avgSGFillTime); + if(this.prevFillTimes.length > this.NUM_POINTS){ + this.prevFillTimes.shift(); + } + } + if(this.SGUpdateTimes.length > 0){ + this.prevUpdateTimes.push(this.avgSGUpdateTime); + if(this.prevUpdateTimes.length > this.NUM_POINTS){ + this.prevUpdateTimes.shift(); + } + } + if(this.SGQueryTimes.length > 0){ + this.prevQueryTimes.push(this.avgSGQueryTime); + if(this.prevQueryTimes.length > this.NUM_POINTS){ + this.prevQueryTimes.shift(); + } + } + + this.updateSGStats(); + } + + static log(key: string, data: any): void { + if(key === "sgclear"){ + this.SGClearTimes.push(data); + if(this.SGClearTimes.length > 100){ + this.SGClearTimes.shift(); + } + } else if(key === "sgfill"){ + this.SGFillTimes.push(data); + if(this.SGFillTimes.length > 100){ + this.SGFillTimes.shift(); + } + } else if(key === "sgupdate"){ + this.SGUpdateTimes.push(data); + if(this.SGUpdateTimes.length > 100){ + this.SGUpdateTimes.shift(); + } + } else if(key === "sgquery"){ + this.SGQueryTimes.push(data); + if(this.SGQueryTimes.length > 1000){ + this.SGQueryTimes.shift(); + } + } + + } + + static render(): void { + // Display stats + this.drawCharts(); + } + + static drawCharts(){ + this.ctx.clearRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT); + + let paramString = this.graphChoices.value; + + if(paramString === "prevfps" || paramString === "all"){ + let param = this.prevfps; + let color = Color.BLUE.toString(); + this.drawChart(param, color); + } + if(paramString === "prevClearTimes" || paramString === "all"){ + let param = this.prevClearTimes; + let color = Color.RED.toString(); + this.drawChart(param, color); + } + if(paramString === "prevFillTimes" || paramString === "all"){ + let param = this.prevFillTimes; + let color = Color.GREEN.toString(); + this.drawChart(param, color); + } + if(paramString === "prevUpdateTimes" || paramString === "all"){ + let param = this.prevUpdateTimes; + let color = Color.CYAN.toString(); + this.drawChart(param, color); + } + if(paramString === "prevQueryTimes" || paramString === "all"){ + let param = this.prevQueryTimes; + let color = Color.ORANGE.toString(); + this.drawChart(param, color); + } + } + + static drawChart(param: Array, color: string){ + this.ctx.strokeStyle = Color.BLACK.toString(); + this.ctx.beginPath(); + this.ctx.moveTo(10, 10); + this.ctx.lineTo(10, this.CANVAS_HEIGHT - 10); + this.ctx.closePath(); + this.ctx.stroke(); + this.ctx.beginPath(); + this.ctx.moveTo(10, this.CANVAS_HEIGHT - 10); + this.ctx.lineTo(this.CANVAS_WIDTH - 10, this.CANVAS_HEIGHT - 10); + this.ctx.closePath(); + this.ctx.stroke(); + + let max = Math.max(...param); + let prevX = 10; + let prevY = this.CANVAS_HEIGHT - 10 - param[0]/max*(this.CANVAS_HEIGHT-20); + this.ctx.strokeStyle = color; + + for(let i = 1; i < param.length; i++){ + let fps = param[i]; + let x = 10 + i*(this.CANVAS_WIDTH - 20)/this.NUM_POINTS; + let y = this.CANVAS_HEIGHT - 10 - fps/max*(this.CANVAS_HEIGHT-20) + this.ctx.beginPath(); + this.ctx.moveTo(prevX, prevY); + this.ctx.lineTo(x, y); + this.ctx.closePath(); + this.ctx.stroke(); + + prevX = x; + prevY = y; + } + } + + static updateSGStats(){ + if(this.SGClearTimes.length > 0){ + this.avgSGClearTime = this.SGClearTimes.reduce((acc, val) => acc + val)/this.SGClearTimes.length; + } + + if(this.SGFillTimes.length > 0){ + this.avgSGFillTime = this.SGFillTimes.reduce((acc, val) => acc + val)/this.SGFillTimes.length; + } + + if(this.SGUpdateTimes.length > 0){ + this.avgSGUpdateTime = this.SGUpdateTimes.reduce((acc, val) => acc + val)/this.SGUpdateTimes.length; + } + + if(this.SGQueryTimes.length > 0){ + this.avgSGQueryTime = this.SGQueryTimes.reduce((acc, val) => acc + val)/this.SGQueryTimes.length; + } + + document.getElementById("sgclear").innerHTML = "Avg SG clear time: " + this.avgSGClearTime; + document.getElementById("sgfill").innerHTML = "Avg SG fill time: " + this.avgSGFillTime; + document.getElementById("sgupdate").innerHTML = "Avg SG update time: " + this.avgSGUpdateTime; + document.getElementById("sgquery").innerHTML = "Avg SG query time: " + this.avgSGQueryTime; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Events/Emitter.ts b/hw3/src/Wolfie2D/Events/Emitter.ts new file mode 100644 index 0000000..ce6abc5 --- /dev/null +++ b/hw3/src/Wolfie2D/Events/Emitter.ts @@ -0,0 +1,26 @@ +import Map from "../DataTypes/Map"; +import EventQueue from "./EventQueue"; +import GameEvent from "./GameEvent"; + +/** + * An event emitter object other systems can use to hook into the EventQueue. + * Provides an easy interface for firing off events. + */ +export default class Emitter { + /** A reference to the EventQueue */ + private eventQueue: EventQueue; + + /** Creates a new Emitter */ + constructor(){ + this.eventQueue = EventQueue.getInstance(); + } + + /** + * Emit and event of type eventType with the data packet data + * @param eventType The name of the event to fire off + * @param data A @reference[Map] or record containing any data about the event + */ + fireEvent(eventType: string, data: Map | Record = null): void { + this.eventQueue.addEvent(new GameEvent(eventType, data)); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Events/EventQueue.ts b/hw3/src/Wolfie2D/Events/EventQueue.ts new file mode 100644 index 0000000..f6718dc --- /dev/null +++ b/hw3/src/Wolfie2D/Events/EventQueue.ts @@ -0,0 +1,125 @@ +import Queue from "../DataTypes/Queue"; +import Map from "../DataTypes/Map"; +import GameEvent from "./GameEvent"; +import Receiver from "./Receiver"; +import { GameEventType } from "./GameEventType"; + +/** + * The main event system of the game engine. + * Events are sent to the EventQueue, which handles distribution to any systems that are listening for those events. + * This allows for handling of input without having classes directly hook into javascript event handles, + * and allows otherwise separate classes to communicate with each other cleanly, such as a Player object + * requesting a sound be played by the audio system. + * + * The distribution of @reference[GameEvent]s happens as follows: + * + * Events are recieved throughout a frame and are queued up by the EventQueue. + * At the beginning of the next frame, events are sent out to any receivers that are hooked into the event type. + * @reference[Receiver]s are then free to process events as they see fit. + * + * Overall, the EventQueue can be considered as something similar to an email server, + * and the @reference[Receiver]s can be considered as the client inboxes. + * + * See @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/event-queue.html) for more discussion on EventQueues + */ +export default class EventQueue { + private static instance: EventQueue = null; + + /** The maximum number of events visible */ + private readonly MAX_SIZE: number; + + /** The actual queue of events */ + private q: Queue; + + /** The map of receivers registered for an event name */ + private receivers: Map>; + + private constructor(){ + this.MAX_SIZE = 100; + this.q = new Queue(this.MAX_SIZE); + this.receivers = new Map>(); + } + + /** Retrieves the instance of the Singleton EventQueue */ + static getInstance(): EventQueue { + if(this.instance === null){ + this.instance = new EventQueue(); + } + + return this.instance; + } + + /** Adds an event to the EventQueue. + * This is exposed to the rest of the game engine through the @reference[Emitter] class */ + addEvent(event: GameEvent): void { + this.q.enqueue(event); + } + + /** + * Associates a receiver with a type of event. Every time this event appears in the future, + * it will be given to the receiver (and any others watching that type). + * This is exposed to the rest of the game engine through the @reference[Receiver] class + * @param receiver The event receiver + * @param type The type or types of events to subscribe to + */ + subscribe(receiver: Receiver, type: string | Array): void { + if(type instanceof Array){ + // If it is an array, subscribe to all event types + for(let t of type){ + this.addListener(receiver, t); + } + } else { + this.addListener(receiver, type); + } + } + + /** + * Unsubscribes the specified receiver from all events, or from whatever events are provided + * @param receiver The receiver to unsubscribe + * @param keys The events to unsubscribe from. If none are provided, unsubscribe from all + */ + unsubscribe(receiver: Receiver, ...events: Array): void { + this.receivers.forEach(eventName => { + // If keys were provided, only continue if this key is one of them + if(events.length > 0 && events.indexOf(eventName) === -1) return; + + // Find the index of our receiver for this key + let index = this.receivers.get(eventName).indexOf(receiver); + + // If an index was found, remove the receiver + if(index !== -1){ + this.receivers.get(eventName).splice(index, 1); + } + }); + } + + // Associate the receiver and the type + private addListener(receiver: Receiver, type: string): void { + if(this.receivers.has(type)){ + this.receivers.get(type).push(receiver); + } else { + this.receivers.add(type, [receiver]); + } + } + + update(deltaT: number): void { + while(this.q.hasItems()){ + // Retrieve each event + let event = this.q.dequeue(); + + // If a receiver has this event type, send it the event + if(this.receivers.has(event.type)){ + for(let receiver of this.receivers.get(event.type)){ + receiver.receive(event); + } + } + + // If a receiver is subscribed to all events, send it the event + if(this.receivers.has(GameEventType.ALL)){ + for(let receiver of this.receivers.get(GameEventType.ALL)){ + receiver.receive(event); + } + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Events/GameEvent.ts b/hw3/src/Wolfie2D/Events/GameEvent.ts new file mode 100644 index 0000000..93eaa21 --- /dev/null +++ b/hw3/src/Wolfie2D/Events/GameEvent.ts @@ -0,0 +1,54 @@ +import Map from "../DataTypes/Map" + +/** + * A representation of an in-game event that is passed through the @reference[EventQueue] + */ +export default class GameEvent { + /** The type of the event */ + public type: string; + /** The data contained by the event */ + public data: Map; + /** The time of the event in ms */ + public time: number; + + /** + * Creates a new GameEvent. + * This is handled implicitly through the @reference[Emitter] class + * @param type The type of the GameEvent + * @param data The data contained by the GameEvent + */ + constructor(type: string, data: Map | Record = null) { + // Parse the game event data + if (data === null) { + this.data = new Map(); + } else if (!(data instanceof Map)){ + // data is a raw object, unpack + this.data = new Map(); + for(let key in data){ + this.data.add(key, data[key]); + } + } else { + this.data = data; + } + + this.type = type; + this.time = Date.now(); + } + + /** + * Checks the type of the GameEvent + * @param type The type to check + * @returns True if the GameEvent is the specified type, false otherwise. + */ + isType(type: string): boolean { + return this.type === type; + } + + /** + * Returns this GameEvent as a string + * @returns The string representation of the GameEvent + */ + toString(): string { + return this.type + ": @" + this.time; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Events/GameEventType.ts b/hw3/src/Wolfie2D/Events/GameEventType.ts new file mode 100644 index 0000000..7f83505 --- /dev/null +++ b/hw3/src/Wolfie2D/Events/GameEventType.ts @@ -0,0 +1,91 @@ +// @ignorePage + +export enum GameEventType { + /** + * Mouse Down event. Has data: {position: Vec2 - Mouse Position} + */ + MOUSE_DOWN = "mouse_down", + /** + * Mouse Up event. Has data: {position: Vec2 - Mouse Position} + */ + MOUSE_UP = "mouse_up", + /** + * Mouse Move event. Has data: {position: Vec2 - Mouse Position} + */ + MOUSE_MOVE = "mouse_move", + + /** + * Key Down event. Has data: {key: string - The key that is down} + */ + KEY_DOWN = "key_down", + + /** + * Key Up event. Has data: {key: string - The key that is up} + */ + KEY_UP = "key_up", + + /** + * Canvas Blur event. Has data: {} + */ + CANVAS_BLUR = "canvas_blur", + + /** + * Mouse wheel up event. Has data: {} + */ + WHEEL_UP = "wheel_up", + + /** + * Mouse wheel down event. Has data: {} + */ + WHEEL_DOWN = "wheel_down", + + /** + * Start Recording event. Has data: {} + */ + START_RECORDING = "start_recording", + + /** + * Stop Recording event. Has data: {} + */ + STOP_RECORDING = "stop_recording", + + /** + * Play Recording event. Has data: {} + */ + PLAY_RECORDING = "play_recording", + + /** + * Play Sound event. Has data: {key: string, loop: boolean, holdReference: boolean } + */ + PLAY_SOUND = "play_sound", + + /** + * Play Sound event. Has data: {key: string} + */ + STOP_SOUND = "stop_sound", + + /** + * Play Sound event. Has data: {key: string, loop: boolean, holdReference: boolean, channel: AudioChannelType } + */ + PLAY_SFX = "play_sfx", + + /** + * Play Sound event. Has data: {key: string, loop: boolean, holdReference: boolean } + */ + PLAY_MUSIC = "play_music", + + /** + * Mute audio channel event. Has data: {channel: AudioChannelType} + */ + MUTE_CHANNEL = "mute_channel", + + /** + * Unmute audio channel event. Has data: {channel: AudioChannelType} + */ + UNMUTE_CHANNEL = "unmute_channel", + + /** + * Encompasses all event types. Used for receivers only. + */ + ALL = "all", +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Events/Receiver.ts b/hw3/src/Wolfie2D/Events/Receiver.ts new file mode 100644 index 0000000..3b3f680 --- /dev/null +++ b/hw3/src/Wolfie2D/Events/Receiver.ts @@ -0,0 +1,77 @@ +import Queue from "../DataTypes/Queue"; +import EventQueue from "./EventQueue"; +import GameEvent from "./GameEvent"; + +/** + * Receives subscribed events from the EventQueue. + */ +export default class Receiver { + /** The maximum number of events this Receiver can hold at one time */ + readonly MAX_SIZE: number; + + /** The inbox of the Receiver */ + private q: Queue; + + /** Creates a new Receiver */ + constructor(){ + this.MAX_SIZE = 100; + this.q = new Queue(this.MAX_SIZE); + } + + destroy(){ + EventQueue.getInstance().unsubscribe(this); + } + + /** + * Adds these types of events to this receiver's queue every update. + * @param eventTypes The types of events this receiver will be subscribed to + */ + subscribe(eventTypes: string | Array): void { + EventQueue.getInstance().subscribe(this, eventTypes); + this.q.clear(); + } + + /** + * Adds an event to the queue of this reciever. This is used by the @reference[EventQueue] to distribute events + * @param event The event to receive + */ + receive(event: GameEvent): void { + try{ + this.q.enqueue(event); + } catch(e){ + console.warn("Receiver overflow for event " + event.toString()); + throw e; + } + } + + /** + * Retrieves the next event from the receiver's queue + * @returns The next GameEvent + */ + getNextEvent(): GameEvent { + return this.q.dequeue(); + } + + /** + * Looks at the next event in the receiver's queue, but doesn't remove it from the queue + * @returns The next GameEvent + */ + peekNextEvent(): GameEvent { + return this.q.peekNext() + } + + /** + * Returns true if the receiver has any events in its queue + * @returns True if the receiver has another event, false otherwise + */ + hasNextEvent(): boolean { + return this.q.hasItems(); + } + + /** + * Ignore all events this frame + */ + ignoreEvents(): void { + this.q.clear(); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Input/Input.ts b/hw3/src/Wolfie2D/Input/Input.ts new file mode 100644 index 0000000..b9c7720 --- /dev/null +++ b/hw3/src/Wolfie2D/Input/Input.ts @@ -0,0 +1,322 @@ +import Receiver from "../Events/Receiver"; +import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import EventQueue from "../Events/EventQueue"; +import Viewport from "../SceneGraph/Viewport"; +import GameEvent from "../Events/GameEvent"; +import { GameEventType } from "../Events/GameEventType"; + +/** + * Receives input events from the @reference[EventQueue] and allows for easy access of information about input by other systems + */ +export default class Input { + private static mousePressed: boolean; + private static mouseJustPressed: boolean; + + private static keyJustPressed: Map; + private static keyPressed: Map; + + private static mousePosition: Vec2; + private static mousePressPosition: Vec2; + + private static scrollDirection: number; + private static justScrolled: boolean; + + private static eventQueue: EventQueue; + private static receiver: Receiver; + private static viewport: Viewport; + + private static keyMap: Map>; + + private static keysDisabled: boolean; + private static mouseDisabled: boolean; + + /** + * Initializes the Input object + * @param viewport A reference to the viewport of the game + */ + static initialize(viewport: Viewport, keyMap: Array>){ + Input.viewport = viewport; + Input.mousePressed = false; + Input.mouseJustPressed = false; + Input.receiver = new Receiver(); + Input.keyJustPressed = new Map(); + Input.keyPressed = new Map(); + Input.mousePosition = new Vec2(0, 0); + Input.mousePressPosition = new Vec2(0, 0); + Input.scrollDirection = 0; + Input.justScrolled = false; + Input.keysDisabled = false; + Input.mouseDisabled = false; + + // Initialize the keymap + Input.keyMap = new Map(); + + // Add all keys to the keymap + for(let entry in keyMap){ + let name = keyMap[entry].name; + let keys = keyMap[entry].keys; + Input.keyMap.add(name, keys); + } + + Input.eventQueue = EventQueue.getInstance(); + // Subscribe to all input events + Input.eventQueue.subscribe(Input.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE, + GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]); + } + + static update(deltaT: number): void { + // Reset the justPressed values to false + Input.mouseJustPressed = false; + Input.keyJustPressed.forEach((key: string) => Input.keyJustPressed.set(key, false)); + Input.justScrolled = false; + Input.scrollDirection = 0; + + while(Input.receiver.hasNextEvent()){ + let event = Input.receiver.getNextEvent(); + + // Handle each event type + if(event.type === GameEventType.MOUSE_DOWN){ + Input.mouseJustPressed = true; + Input.mousePressed = true; + Input.mousePressPosition = event.data.get("position"); + } + + if(event.type === GameEventType.MOUSE_UP){ + Input.mousePressed = false; + } + + if(event.type === GameEventType.MOUSE_MOVE){ + Input.mousePosition = event.data.get("position"); + } + + if(event.type === GameEventType.KEY_DOWN){ + let key = event.data.get("key"); + // Handle space bar + if(key === " "){ + key = "space"; + } + if(!Input.keyPressed.get(key)){ + Input.keyJustPressed.set(key, true); + Input.keyPressed.set(key, true); + } + } + + if(event.type === GameEventType.KEY_UP){ + let key = event.data.get("key"); + // Handle space bar + if(key === " "){ + key = "space"; + } + Input.keyPressed.set(key, false); + } + + if(event.type === GameEventType.CANVAS_BLUR){ + Input.clearKeyPresses() + } + + if(event.type === GameEventType.WHEEL_UP){ + Input.scrollDirection = -1; + Input.justScrolled = true; + } else if(event.type === GameEventType.WHEEL_DOWN){ + Input.scrollDirection = 1; + Input.justScrolled = true; + } + } + } + + private static clearKeyPresses(): void { + Input.keyJustPressed.forEach((key: string) => Input.keyJustPressed.set(key, false)); + Input.keyPressed.forEach((key: string) => Input.keyPressed.set(key, false)); + } + + /** + * Returns whether or not a key was newly pressed Input frame. + * If the key is still pressed from last frame and wasn't re-pressed, Input will return false. + * @param key The key + * @returns True if the key was just pressed, false otherwise + */ + static isKeyJustPressed(key: string): boolean { + if(Input.keysDisabled) return false; + + if(Input.keyJustPressed.has(key)){ + return Input.keyJustPressed.get(key) + } else { + return false; + } + } + + /** + * Returns an array of all of the keys that are newly pressed Input frame. + * If a key is still pressed from last frame and wasn't re-pressed, it will not be in Input list. + * @returns An array of all of the newly pressed keys. + */ + static getKeysJustPressed(): Array { + if(Input.keysDisabled) return []; + + let keys = Array(); + Input.keyJustPressed.forEach(key => { + if(Input.keyJustPressed.get(key)){ + keys.push(key); + } + }); + return keys; + } + + /** + * Returns whether or not a key is being pressed. + * @param key The key + * @returns True if the key is currently pressed, false otherwise + */ + static isKeyPressed(key: string): boolean { + if(Input.keysDisabled) return false; + + if(Input.keyPressed.has(key)){ + return Input.keyPressed.get(key) + } else { + return false; + } + } + + /** + * Changes the binding of an input name to keys + * @param inputName The name of the input + * @param keys The corresponding keys + */ + static changeKeyBinding(inputName: string, keys: Array): void { + Input.keyMap.set(inputName, keys); + } + + /** + * Clears all key bindings + */ + static clearAllKeyBindings(): void { + Input.keyMap.clear(); + } + + /** + * Returns whether or not an input was just pressed this frame + * @param inputName The name of the input + * @returns True if the input was just pressed, false otherwise + */ + static isJustPressed(inputName: string): boolean { + if(Input.keysDisabled) return false; + + if(Input.keyMap.has(inputName)){ + const keys = Input.keyMap.get(inputName); + let justPressed = false; + + for(let key of keys){ + justPressed = justPressed || Input.isKeyJustPressed(key); + } + + return justPressed; + } else { + return false; + } + } + + /** + * Returns whether or not an input is currently pressed + * @param inputName The name of the input + * @returns True if the input is pressed, false otherwise + */ + static isPressed(inputName: string): boolean { + if(Input.keysDisabled) return false; + + if(Input.keyMap.has(inputName)){ + const keys = Input.keyMap.get(inputName); + let pressed = false; + + for(let key of keys){ + pressed = pressed || Input.isKeyPressed(key); + } + + return pressed; + } else { + return false; + } + } + + /** + * Returns whether or not the mouse was newly pressed Input frame + * @returns True if the mouse was just pressed, false otherwise + */ + static isMouseJustPressed(): boolean { + return Input.mouseJustPressed && !Input.mouseDisabled; + } + + /** + * Returns whether or not the mouse is currently pressed + * @returns True if the mouse is currently pressed, false otherwise + */ + static isMousePressed(): boolean { + return Input.mousePressed && !Input.mouseDisabled; + } + + /** + * Returns whether the user scrolled or not + * @returns True if the user just scrolled Input frame, false otherwise + */ + static didJustScroll(): boolean { + return Input.justScrolled && !Input.mouseDisabled; + } + + /** + * Gets the direction of the scroll + * @returns -1 if the user scrolled up, 1 if they scrolled down + */ + static getScrollDirection(): number { + return Input.scrollDirection; + } + + /** + * Gets the position of the player's mouse + * @returns The mouse position stored as a Vec2 + */ + static getMousePosition(): Vec2 { + return Input.mousePosition.scaled(1/this.viewport.getZoomLevel()); + } + + /** + * Gets the position of the player's mouse in the game world, + * taking into consideration the scrolling of the viewport + * @returns The mouse position stored as a Vec2 + */ + static getGlobalMousePosition(): Vec2 { + return Input.mousePosition.clone().scale(1/this.viewport.getZoomLevel()).add(Input.viewport.getOrigin()); + } + + /** + * Gets the position of the last mouse press + * @returns The mouse position stored as a Vec2 + */ + static getMousePressPosition(): Vec2 { + return Input.mousePressPosition; + } + + /** + * Gets the position of the last mouse press in the game world, + * taking into consideration the scrolling of the viewport + * @returns The mouse position stored as a Vec2 + */ + static getGlobalMousePressPosition(): Vec2 { + return Input.mousePressPosition.clone().add(Input.viewport.getOrigin()); + } + + /** + * Disables all keypress and mouse click inputs + */ + static disableInput(): void { + Input.keysDisabled = true; + Input.mouseDisabled = true; + } + + /** + * Enables all keypress and mouse click inputs + */ + static enableInput(): void { + Input.keysDisabled = false; + Input.mouseDisabled = false; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Input/InputHandler.ts b/hw3/src/Wolfie2D/Input/InputHandler.ts new file mode 100644 index 0000000..4d34811 --- /dev/null +++ b/hw3/src/Wolfie2D/Input/InputHandler.ts @@ -0,0 +1,93 @@ +import EventQueue from "../Events/EventQueue"; +import Vec2 from "../DataTypes/Vec2"; +import GameEvent from "../Events/GameEvent"; +import { GameEventType } from "../Events/GameEventType"; + +/** + * Handles communication with the web browser to receive asynchronous events and send them to the @reference[EventQueue] + */ +export default class InputHandler { + private eventQueue: EventQueue; + + /** + * Creates a new InputHandler + * @param canvas The game canvas + */ + constructor(canvas: HTMLCanvasElement){ + this.eventQueue = EventQueue.getInstance(); + + canvas.onmousedown = (event) => this.handleMouseDown(event, canvas); + canvas.onmouseup = (event) => this.handleMouseUp(event, canvas); + canvas.oncontextmenu = this.handleContextMenu; + canvas.onmousemove = (event) => this.handleMouseMove(event, canvas); + document.onkeydown = this.handleKeyDown; + document.onkeyup = this.handleKeyUp; + document.onblur = this.handleBlur; + document.oncontextmenu = this.handleBlur; + document.onwheel = this.handleWheel; + } + + private handleMouseDown = (event: MouseEvent, canvas: HTMLCanvasElement): void => { + let pos = this.getMousePosition(event, canvas); + let gameEvent = new GameEvent(GameEventType.MOUSE_DOWN, {position: pos}); + this.eventQueue.addEvent(gameEvent); + } + + private handleMouseUp = (event: MouseEvent, canvas: HTMLCanvasElement): void => { + let pos = this.getMousePosition(event, canvas); + let gameEvent = new GameEvent(GameEventType.MOUSE_UP, {position: pos}); + this.eventQueue.addEvent(gameEvent); + } + + private handleMouseMove = (event: MouseEvent, canvas: HTMLCanvasElement): void => { + let pos = this.getMousePosition(event, canvas); + let gameEvent = new GameEvent(GameEventType.MOUSE_MOVE, {position: pos}); + this.eventQueue.addEvent(gameEvent); + } + + private handleKeyDown = (event: KeyboardEvent): void => { + let key = this.getKey(event); + let gameEvent = new GameEvent(GameEventType.KEY_DOWN, {key: key}); + this.eventQueue.addEvent(gameEvent); + } + + private handleKeyUp = (event: KeyboardEvent): void => { + let key = this.getKey(event); + let gameEvent = new GameEvent(GameEventType.KEY_UP, {key: key}); + this.eventQueue.addEvent(gameEvent); + } + + private handleBlur = (event: Event): void => { + let gameEvent = new GameEvent(GameEventType.CANVAS_BLUR, {}); + this.eventQueue.addEvent(gameEvent); + } + + private handleContextMenu = (event: Event): void => { + event.preventDefault(); + event.stopPropagation(); + } + + private handleWheel = (event: WheelEvent): void => { + event.preventDefault(); + event.stopPropagation(); + + let gameEvent: GameEvent; + if(event.deltaY < 0){ + gameEvent = new GameEvent(GameEventType.WHEEL_UP, {}); + } else { + gameEvent = new GameEvent(GameEventType.WHEEL_DOWN, {}); + } + this.eventQueue.addEvent(gameEvent); + } + + private getKey(keyEvent: KeyboardEvent){ + return keyEvent.key.toLowerCase(); + } + + private getMousePosition(mouseEvent: MouseEvent, canvas: HTMLCanvasElement): Vec2 { + let rect = canvas.getBoundingClientRect(); + let x = mouseEvent.clientX - rect.left; + let y = mouseEvent.clientY - rect.top; + return new Vec2(x, y); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Loop/EnvironmentInitializer.ts b/hw3/src/Wolfie2D/Loop/EnvironmentInitializer.ts new file mode 100644 index 0000000..ff73726 --- /dev/null +++ b/hw3/src/Wolfie2D/Loop/EnvironmentInitializer.ts @@ -0,0 +1,47 @@ +import {} from "../../index"; // This import allows us to modify the CanvasRenderingContext2D to add extra functionality +// @ignorePage + +/** + * Sets up the environment of the game engine + */ +export default class EnvironmentInitializer { + static setup(){ + CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void { + // Clamp the radius between 0 and the min of the width or height + if(r < 0) r = 0; + if(r > Math.min(w, h)) r = Math.min(w, h); + + // Draw the rounded rect + this.beginPath(); + + // Top + this.moveTo(x + r, y); + this.lineTo(x + w - r, y); + this.arcTo(x + w, y, x + w, y + r, r); + + // Right + this.lineTo(x + w, y + h - r); + this.arcTo(x + w, y + h, x + w - r, y + h, r); + + // Bottom + this.lineTo(x + r, y + h); + this.arcTo(x, y + h, x, y + h - r, r); + + // Left + this.lineTo(x, y + r); + this.arcTo(x, y, x + r, y, r) + + this.closePath(); + } + + CanvasRenderingContext2D.prototype.strokeRoundedRect = function(x, y, w, h, r){ + this.roundedRect(x, y, w, h, r); + this.stroke(); + } + + CanvasRenderingContext2D.prototype.fillRoundedRect = function(x, y, w, h, r){ + this.roundedRect(x, y, w, h, r); + this.fill(); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Loop/FixedUpdateGameLoop.ts b/hw3/src/Wolfie2D/Loop/FixedUpdateGameLoop.ts new file mode 100644 index 0000000..49abe5c --- /dev/null +++ b/hw3/src/Wolfie2D/Loop/FixedUpdateGameLoop.ts @@ -0,0 +1,235 @@ +import GameLoop from "./GameLoop"; +import Debug from "../Debug/Debug"; +import Stats from "../Debug/Stats"; + +/** + * A game loop with a fixed update time and a variable render time. + * Every frame, the game updates until all time since the last frame has been processed. + * If too much time has passed, such as if the last update was too slow, + * or if the browser was put into the background, the loop will panic and discard time. + * A render happens at the end of every frame. This happens as fast as possible unless specified. + * A loop of this type allows for deterministic behavior - No matter what the frame rate is, the update should behave the same, + * as it is occuring in a fixed interval. + */ +export default class FixedUpdateGameLoop extends GameLoop { + + /** The max allowed update fps.*/ + private maxUpdateFPS: number; + + /** The timestep for each update. This is the deltaT passed to update calls. */ + private updateTimestep: number; + + /** The amount of time we are yet to simulate. */ + private frameDelta: number; + + /** The time when the last frame was drawn. */ + private lastFrameTime: number; + + /** The minimum time we want to wait between game frames. */ + private minFrameDelay: number; + + /** The current frame of the game. */ + private frame: number; + + /** The actual fps of the game. */ + private fps: number; + + /** The time between fps measurement updates. */ + private fpsUpdateInterval: number; + + /** The time of the last fps update. */ + private lastFpsUpdate: number; + + /** The number of frames since the last fps update was done. */ + private framesSinceLastFpsUpdate: number; + + /** The status of whether or not the game loop has started. */ + private started: boolean; + + /** The status of whether or not the game loop is paused */ + private paused: boolean; + + /** The status of whether or not the game loop is currently running. */ + private running: boolean; + + /** The number of update steps this iteration of the game loop. */ + private numUpdateSteps: number; + + constructor() { + super(); + this.maxUpdateFPS = 60; + this.updateTimestep = Math.floor(1000/this.maxUpdateFPS); + this.frameDelta = 0; + this.lastFrameTime = 0; + this.minFrameDelay = 0; + this.frame = 0; + this.fps = this.maxUpdateFPS; // Initialize the fps to the max allowed fps + this.fpsUpdateInterval = 1000; + this.lastFpsUpdate = 0; + this.framesSinceLastFpsUpdate = 0; + this.started = false; + this.paused = false; + this.running = false; + this.numUpdateSteps = 0; + } + + getFPS(): number { + return 0; + } + + /** + * Updates the frame count and sum of time for the framerate of the game + * @param timestep The current time in ms + */ + protected updateFPS(timestamp: number): void { + this.fps = 0.9 * this.framesSinceLastFpsUpdate * 1000 / (timestamp - this.lastFpsUpdate) +(1 - 0.9) * this.fps; + this.lastFpsUpdate = timestamp; + this.framesSinceLastFpsUpdate = 0; + + Debug.log("fps", "FPS: " + this.fps.toFixed(1)); + Stats.updateFPS(this.fps); + } + + /** + * Changes the maximum allowed physics framerate of the game + * @param initMax The max framerate + */ + setMaxUpdateFPS(initMax: number): void { + this.maxUpdateFPS = initMax; + this.updateTimestep = Math.floor(1000/this.maxUpdateFPS); + } + + /** + * Sets the maximum rendering framerate + * @param maxFPS The max framerate + */ + setMaxFPS(maxFPS: number): void { + this.minFrameDelay = 1000/maxFPS; + } + + /** + * This function is called when the game loop panics, i.e. it tries to process too much time in an entire frame. + * This will reset the amount of time back to zero. + * @returns The amount of time we are discarding from processing. + */ + resetFrameDelta() : number { + let oldFrameDelta = this.frameDelta; + this.frameDelta = 0; + return oldFrameDelta; + } + + /** + * Starts up the game loop and calls the first requestAnimationFrame + */ + start(): void { + if(!this.started){ + this.started = true; + + window.requestAnimationFrame((timestamp) => this.doFirstFrame(timestamp)); + } + } + + pause(): void { + this.paused = true; + } + + resume(): void { + this.paused = false; + } + + /** + * The first game frame - initializes the first frame time and begins the render + * @param timestamp The current time in ms + */ + protected doFirstFrame(timestamp: number): void { + this.running = true; + + this._doRender(); + + this.lastFrameTime = timestamp; + this.lastFpsUpdate = timestamp; + this.framesSinceLastFpsUpdate = 0; + + window.requestAnimationFrame((t) => this.doFrame(t)); + } + + /** + * Handles any processing that needs to be done at the start of the frame + * @param timestamp The time of the frame in ms + */ + protected startFrame(timestamp: number): void { + // Update the amount of time we need our update to process + this.frameDelta += timestamp - this.lastFrameTime; + + // Set the new time of the last frame + this.lastFrameTime = timestamp; + + // Update the estimate of the framerate + if(timestamp > this.lastFpsUpdate + this.fpsUpdateInterval){ + this.updateFPS(timestamp); + } + + // Increment the number of frames + this.frame++; + this.framesSinceLastFpsUpdate++; + } + + /** + * The main loop of the game. Updates until the current time is reached. Renders once + * @param timestamp The current time in ms + */ + protected doFrame = (timestamp: number): void => { + // If a pause was executed, stop doing the loop. + if(this.paused){ + return; + } + + // Request animation frame to prepare for another update or render + window.requestAnimationFrame((t) => this.doFrame(t)); + + // If we are trying to render too soon, do nothing. + if(timestamp < this.lastFrameTime + this.minFrameDelay){ + return; + } + + // A frame is actually happening + this.startFrame(timestamp); + + // Update while there is still time to make up. If we do too many update steps, panic and exit the loop. + this.numUpdateSteps = 0; + let panic = false; + + while(this.frameDelta >= this.updateTimestep){ + // Do an update + this._doUpdate(this.updateTimestep/1000); + + // Remove the update step time from the time we have to process + this.frameDelta -= this.updateTimestep; + + // Increment steps and check if we've done too many + this.numUpdateSteps++; + if(this.numUpdateSteps > 100){ + panic = true; + break; + } + } + + // Updates are done, render + this._doRender(); + + // Wrap up the frame + this.finishFrame(panic); + } + + /** + * Wraps up the frame and handles the panic state if there is one + * @param panic Whether or not the loop panicked + */ + protected finishFrame(panic: boolean): void { + if(panic) { + var discardedTime = Math.round(this.resetFrameDelta()); + console.warn('Main loop panicked, probably because the browser tab was put in the background. Discarding ' + discardedTime + 'ms'); + } + } + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Loop/Game.ts b/hw3/src/Wolfie2D/Loop/Game.ts new file mode 100644 index 0000000..0014bdb --- /dev/null +++ b/hw3/src/Wolfie2D/Loop/Game.ts @@ -0,0 +1,214 @@ +import EventQueue from "../Events/EventQueue"; +import Input from "../Input/Input"; +import InputHandler from "../Input/InputHandler"; +import Recorder from "../Playback/Recorder"; +import Debug from "../Debug/Debug"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import Viewport from "../SceneGraph/Viewport"; +import SceneManager from "../Scene/SceneManager"; +import AudioManager from "../Sound/AudioManager"; +import Stats from "../Debug/Stats"; +import RenderingManager from "../Rendering/RenderingManager"; +import CanvasRenderer from "../Rendering/CanvasRenderer"; +import Color from "../Utils/Color"; +import GameOptions from "./GameOptions"; +import GameLoop from "./GameLoop"; +import FixedUpdateGameLoop from "./FixedUpdateGameLoop"; +import EnvironmentInitializer from "./EnvironmentInitializer"; +import Vec2 from "../DataTypes/Vec2"; +import RegistryManager from "../Registry/RegistryManager"; +import WebGLRenderer from "../Rendering/WebGLRenderer"; +import Scene from "../Scene/Scene"; + +/** + * The main loop of the game engine. + * Handles the update order, and initializes all subsystems. + * The Game manages the update cycle, and requests animation frames to render to the browser. + */ +export default class Game { + gameOptions: GameOptions; + private showDebug: boolean; + private showStats: boolean; + + // The game loop + private loop: GameLoop; + + // Game canvas and its width and height + readonly GAME_CANVAS: HTMLCanvasElement; + readonly DEBUG_CANVAS: HTMLCanvasElement; + readonly WIDTH: number; + readonly HEIGHT: number; + private viewport: Viewport; + private ctx: CanvasRenderingContext2D | WebGLRenderingContext; + private clearColor: Color; + + // All of the necessary subsystems that need to run here + private eventQueue: EventQueue; + private inputHandler: InputHandler; + private recorder: Recorder; + private resourceManager: ResourceManager; + private sceneManager: SceneManager; + private audioManager: AudioManager; + private renderingManager: RenderingManager; + + /** + * Creates a new Game + * @param options The options for Game initialization + */ + constructor(options?: Record){ + // Before anything else, build the environment + EnvironmentInitializer.setup(); + + // Typecast the config object to a GameConfig object + this.gameOptions = GameOptions.parse(options); + + this.showDebug = this.gameOptions.showDebug; + this.showStats = this.gameOptions.showStats; + + // Create an instance of a game loop + this.loop = new FixedUpdateGameLoop(); + + // Get the game canvas and give it a background color + this.GAME_CANVAS = document.getElementById("game-canvas"); + this.DEBUG_CANVAS = document.getElementById("debug-canvas"); + + // Give the canvas a size and get the rendering context + this.WIDTH = this.gameOptions.canvasSize.x; + this.HEIGHT = this.gameOptions.canvasSize.y; + + // This step MUST happen before the resource manager does anything + if(this.gameOptions.useWebGL){ + this.renderingManager = new WebGLRenderer(); + } else { + this.renderingManager = new CanvasRenderer(); + } + this.initializeGameWindow(); + this.ctx = this.renderingManager.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT); + this.clearColor = new Color(this.gameOptions.clearColor.r, this.gameOptions.clearColor.g, this.gameOptions.clearColor.b); + + // Initialize debugging and stats + Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT); + Stats.initStats(); + + if(this.gameOptions.showStats) { + // Find the stats output and make it no longer hidden + document.getElementById("stats").hidden = false; + } + + // Size the viewport to the game canvas + const canvasSize = new Vec2(this.WIDTH, this.HEIGHT); + this.viewport = new Viewport(canvasSize, this.gameOptions.zoomLevel); + + // Initialize all necessary game subsystems + this.eventQueue = EventQueue.getInstance(); + this.inputHandler = new InputHandler(this.GAME_CANVAS); + Input.initialize(this.viewport, this.gameOptions.inputs); + this.recorder = new Recorder(); + this.resourceManager = ResourceManager.getInstance(); + this.sceneManager = new SceneManager(this.viewport, this.renderingManager); + this.audioManager = AudioManager.getInstance(); + } + + /** + * Set up the game window that holds the canvases + */ + private initializeGameWindow(): void { + const gameWindow = document.getElementById("game-window"); + + // Set the height of the game window + gameWindow.style.width = this.WIDTH + "px"; + gameWindow.style.height = this.HEIGHT + "px"; + } + + /** + * Retreives the SceneManager from the Game + * @returns The SceneManager + */ + getSceneManager(): SceneManager { + return this.sceneManager; + } + + /** + * Starts the game + */ + start(InitialScene: new (...args: any) => Scene, options: Record): void { + // Set the update function of the loop + this.loop.doUpdate = (deltaT: number) => this.update(deltaT); + + // Set the render function of the loop + this.loop.doRender = () => this.render(); + + // Preload registry items + RegistryManager.preload(); + + // Load the items with the resource manager + this.resourceManager.loadResourcesFromQueue(() => { + // When we're done loading, start the loop + console.log("Finished Preload - loading first scene"); + this.sceneManager.changeToScene(InitialScene, {}, options); + this.loop.start(); + }); + } + + /** + * Updates all necessary subsystems of the game. Defers scene updates to the sceneManager + * @param deltaT The time sine the last update + */ + update(deltaT: number): void { + try{ + // Handle all events that happened since the start of the last loop + this.eventQueue.update(deltaT); + + // Update the input data structures so game objects can see the input + Input.update(deltaT); + + // Update the recording of the game + this.recorder.update(deltaT); + + // Update all scenes + this.sceneManager.update(deltaT); + + // Update all sounds + this.audioManager.update(deltaT); + + // Load or unload any resources if needed + this.resourceManager.update(deltaT); + } catch(e){ + this.loop.pause(); + console.warn("Uncaught Error in Update - Crashing gracefully"); + console.error(e); + } + } + + /** + * Clears the canvas and defers scene rendering to the sceneManager. Renders the debug canvas + */ + render(): void { + try{ + // Clear the canvases + Debug.clearCanvas(); + + this.renderingManager.clear(this.clearColor); + + this.sceneManager.render(); + + // Hacky debug mode + if(Input.isKeyJustPressed("g")){ + this.showDebug = !this.showDebug; + } + + // Debug render + if(this.showDebug){ + Debug.render(); + } + + if(this.showStats){ + Stats.render(); + } + } catch(e){ + this.loop.pause(); + console.warn("Uncaught Error in Render - Crashing gracefully"); + console.error(e); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Loop/GameLoop.ts b/hw3/src/Wolfie2D/Loop/GameLoop.ts new file mode 100644 index 0000000..ea7da06 --- /dev/null +++ b/hw3/src/Wolfie2D/Loop/GameLoop.ts @@ -0,0 +1,69 @@ +import NullFunc from "../DataTypes/Functions/NullFunc"; + +/** + * The main game loop of the game. Keeps track of fps and handles scheduling of updates and rendering. + * This class is left abstract, so that a subclass can handle exactly how the loop is scheduled. + * For an example of different types of game loop scheduling, check out @link(Game Programming Patterns)(https://gameprogrammingpatterns.com/game-loop.html) + */ +export default abstract class GameLoop { + + /** The function to call when an update occurs */ + protected _doUpdate: Function = NullFunc; + + set doUpdate(update: Function){ + this._doUpdate = update; + } + + /** The function to call when a render occurs */ + protected _doRender: Function = NullFunc; + + + set doRender(render: Function){ + this._doRender = render; + } + + /** + * Retrieves the current FPS of the game + */ + abstract getFPS(): number; + + /** + * Starts up the game loop + */ + abstract start(): void; + + /** + * Pauses the game loop, usually for an error condition. + */ + abstract pause(): void; + + /** + * Resumes the game loop. + */ + abstract resume(): void; + + /** + * Runs the first frame of the game. No update occurs here, only a render. + * This is needed to initialize delta time values + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract doFirstFrame(timestamp: number): void; + + /** + * Run before any updates or the render of a frame. + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract startFrame(timestamp: number): void; + + /** + * The core of the frame, where any necessary updates occur, and where a render happens + * @param timestamp The timestamp of the frame. This is received from the browser + */ + protected abstract doFrame(timestamp: number): void; + + /** + * Wraps up the frame + * @param panic Whether or not the update cycle panicked. This happens when too many updates try to happen in a single frame + */ + protected abstract finishFrame(panic: boolean): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Loop/GameOptions.ts b/hw3/src/Wolfie2D/Loop/GameOptions.ts new file mode 100644 index 0000000..a9112fb --- /dev/null +++ b/hw3/src/Wolfie2D/Loop/GameOptions.ts @@ -0,0 +1,44 @@ +// @ignorePage + +/** The options for initializing the @reference[GameLoop] */ +export default class GameOptions { + /** The size of the viewport */ + canvasSize: {x: number, y: number}; + + /* The default level of zoom */ + zoomLevel: number; + + /** The color to clear the canvas to each frame */ + clearColor: {r: number, g: number, b: number} + + /* A list of input bindings */ + inputs: Array<{name: string, keys: Array}>; + + /* Whether or not the debug rendering should occur */ + showDebug: boolean; + + /* Whether or not the stats rendering should occur */ + showStats: boolean; + + /* Whether or not to use webGL */ + useWebGL: boolean; + + /** + * Parses the data in the raw options object + * @param options The game options as a Record + * @returns A version of the options converted to a GameOptions object + */ + static parse(options: Record): GameOptions { + let gOpt = new GameOptions(); + + gOpt.canvasSize = options.canvasSize ? options.canvasSize : {x: 800, y: 600}; + gOpt.zoomLevel = options.zoomLevel ? options.zoomLevel : 1; + gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255}; + gOpt.inputs = options.inputs ? options.inputs : []; + gOpt.showDebug = !!options.showDebug; + gOpt.showStats = !!options.showStats; + gOpt.useWebGL = !!options.useWebGL; + + return gOpt; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/CanvasNode.ts b/hw3/src/Wolfie2D/Nodes/CanvasNode.ts new file mode 100644 index 0000000..d257b5e --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/CanvasNode.ts @@ -0,0 +1,137 @@ +import GameNode from "./GameNode"; +import Vec2 from "../DataTypes/Vec2"; +import Region from "../DataTypes/Interfaces/Region"; +import AABB from "../DataTypes/Shapes/AABB"; +import Debug from "../Debug/Debug"; +import Color from "../Utils/Color"; + +/** + * The representation of an object in the game world that can be drawn to the screen + */ +export default abstract class CanvasNode extends GameNode implements Region { + private _size: Vec2; + private _scale: Vec2; + private _boundary: AABB; + private _hasCustomShader: boolean; + private _customShaderKey: string; + private _alpha: number; + + /** A flag for whether or not the CanvasNode is visible */ + visible: boolean = true; + + constructor(){ + super(); + this._size = new Vec2(0, 0); + this._size.setOnChange(() => this.sizeChanged()); + this._scale = new Vec2(1, 1); + this._scale.setOnChange(() => this.scaleChanged()); + this._boundary = new AABB(); + this.updateBoundary(); + + this._hasCustomShader = false; + } + + get alpha(): number { + return this._alpha; + } + + set alpha(a: number) { + this._alpha = a; + } + + get size(): Vec2 { + return this._size; + } + + set size(size: Vec2){ + this._size = size; + // Enter as a lambda to bind "this" + this._size.setOnChange(() => this.sizeChanged()); + this.sizeChanged(); + } + + get scale(): Vec2 { + return this._scale; + } + + set scale(scale: Vec2){ + this._scale = scale; + // Enter as a lambda to bind "this" + this._scale.setOnChange(() => this.scaleChanged()); + this.scaleChanged(); + } + + set scaleX(value: number) { + this.scale.x = value; + } + + set scaleY(value: number) { + this.scale.y = value; + } + + get hasCustomShader(): boolean { + return this._hasCustomShader; + } + + get customShaderKey(): string { + return this._customShaderKey; + } + + // @override + protected positionChanged(): void { + super.positionChanged(); + this.updateBoundary(); + } + + /** Called if the size vector is changed or replaced. */ + protected sizeChanged(): void { + this.updateBoundary(); + } + + /** Called if the scale vector is changed or replaced */ + protected scaleChanged(): void { + this.updateBoundary(); + } + + // @docIgnore + /** Called if the position, size, or scale of the CanvasNode is changed. Updates the boundary. */ + private updateBoundary(): void { + this._boundary.center.set(this.position.x, this.position.y); + this._boundary.halfSize.set(this.size.x*this.scale.x/2, this.size.y*this.scale.y/2); + } + + get boundary(): AABB { + return this._boundary; + } + + get sizeWithZoom(): Vec2 { + let zoom = this.scene.getViewScale(); + + return this.boundary.halfSize.clone().scaled(zoom, zoom); + } + + /** + * Adds a custom shader to this CanvasNode + * @param key The registry key of the ShaderType + */ + useCustomShader(key: string): void { + this._hasCustomShader = true; + this._customShaderKey = key; + } + + /** + * Returns true if the point (x, y) is inside of this canvas object + * @param x The x position of the point + * @param y The y position of the point + * @returns A flag representing whether or not this node contains the point. + */ + contains(x: number, y: number): boolean { + return this._boundary.containsPoint(new Vec2(x, y)); + } + + // @implemented + debugRender(): void { + Debug.drawBox(this.relativePosition, this.sizeWithZoom, false, Color.BLUE); + super.debugRender(); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/GameNode.ts b/hw3/src/Wolfie2D/Nodes/GameNode.ts new file mode 100644 index 0000000..6ae3794 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/GameNode.ts @@ -0,0 +1,478 @@ +import Vec2 from "../DataTypes/Vec2"; +import Receiver from "../Events/Receiver"; +import Emitter from "../Events/Emitter"; +import Scene from "../Scene/Scene"; +import Layer from "../Scene/Layer"; +import AI from "../DataTypes/Interfaces/AI"; +import Physical from "../DataTypes/Interfaces/Physical"; +import Positioned from "../DataTypes/Interfaces/Positioned"; +import { isRegion } from "../DataTypes/Interfaces/Region"; +import Unique from "../DataTypes/Interfaces/Unique"; +import Updateable from "../DataTypes/Interfaces/Updateable"; +import DebugRenderable from "../DataTypes/Interfaces/DebugRenderable"; +import Actor from "../DataTypes/Interfaces/Actor"; +import Shape from "../DataTypes/Shapes/Shape"; +import AABB from "../DataTypes/Shapes/AABB"; +import NavigationPath from "../Pathfinding/NavigationPath"; +import TweenController from "../Rendering/Animations/TweenController"; +import Debug from "../Debug/Debug"; +import Color from "../Utils/Color"; +import Circle from "../DataTypes/Shapes/Circle"; + +/** + * The representation of an object in the game world. + * To construct GameNodes, see the @reference[Scene] documentation. + */ +export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor, DebugRenderable { + /*---------- POSITIONED ----------*/ + private _position: Vec2; + + /*---------- UNIQUE ----------*/ + private _id: number; + + /*---------- PHYSICAL ----------*/ + hasPhysics: boolean = false; + moving: boolean = false; + frozen: boolean = false; + onGround: boolean = false; + onWall: boolean = false; + onCeiling: boolean = false; + active: boolean = false; + collisionShape: Shape; + colliderOffset: Vec2; + isStatic: boolean; + isCollidable: boolean; + isTrigger: boolean; + triggerMask: number; + triggerEnters: Array; + triggerExits: Array; + _velocity: Vec2; + sweptRect: AABB; + collidedWithTilemap: boolean; + group: number; + isPlayer: boolean; + isColliding: boolean = false; + + /*---------- ACTOR ----------*/ + _ai: AI; + aiActive: boolean; + path: NavigationPath; + pathfinding: boolean = false; + + /*---------- GENERAL ----------*/ + /** An event receiver. */ + protected receiver: Receiver; + /** An event emitter. */ + protected emitter: Emitter; + /** A reference to the scene this GameNode is a part of. */ + protected scene: Scene; + /** The visual layer this GameNode resides in. */ + protected layer: Layer; + /** A utility that allows the use of tweens on this GameNode */ + tweens: TweenController; + /** A tweenable property for rotation. Does not affect the bounding box of this GameNode - Only rendering. */ + rotation: number; + /** The opacity value of this GameNode */ + abstract set alpha(a: number); + + abstract get alpha(): number; + + // Constructor docs are ignored, as the user should NOT create new GameNodes with a raw constructor + constructor(){ + this._position = new Vec2(0, 0); + this._position.setOnChange(() => this.positionChanged()); + this.receiver = new Receiver(); + this.emitter = new Emitter(); + this.tweens = new TweenController(this); + this.rotation = 0; + } + + destroy(){ + this.tweens.destroy(); + this.receiver.destroy(); + + if(this.hasPhysics){ + this.removePhysics(); + } + + if(this._ai){ + this._ai.destroy(); + delete this._ai; + this.scene.getAIManager().removeActor(this); + } + + this.scene.remove(this); + + this.layer.removeNode(this); + } + + /*---------- POSITIONED ----------*/ + get position(): Vec2 { + return this._position; + } + + set position(pos: Vec2) { + this._position = pos; + this._position.setOnChange(() => this.positionChanged()); + this.positionChanged(); + } + + get relativePosition(): Vec2 { + return this.inRelativeCoordinates(this.position); + } + + /** + * Converts a point to coordinates relative to the zoom and origin of this node + * @param point The point to conver + * @returns A new Vec2 representing the point in relative coordinates + */ + inRelativeCoordinates(point: Vec2): Vec2 { + let origin = this.scene.getViewTranslation(this); + let zoom = this.scene.getViewScale(); + return point.clone().sub(origin).scale(zoom); + } + + /*---------- UNIQUE ----------*/ + get id(): number { + return this._id; + } + + set id(id: number) { + // id can only be set once + if(this._id === undefined){ + this._id = id; + } else { + throw "Attempted to assign id to object that already has id." + } + } + + /*---------- PHYSICAL ----------*/ + // @implemented + /** + * @param velocity The velocity with which to move the object. + */ + move(velocity: Vec2): void { + if(this.frozen) return; + this.moving = true; + this._velocity = velocity; + }; + + moveOnPath(speed: number, path: NavigationPath): void { + if(this.frozen) return; + this.path = path; + let dir = path.getMoveDirection(this); + this.moving = true; + this.pathfinding = true; + this._velocity = dir.scale(speed); + } + + // @implemented + /** + * @param velocity The velocity with which the object will move. + */ + finishMove(): void { + this.moving = false; + this.position.add(this._velocity); + if(this.pathfinding){ + this.path.handlePathProgress(this); + this.path = null; + this.pathfinding = false; + } + } + + // @implemented + /** + * @param collisionShape The collider for this object. If this has a region (implements Region), + * it will be used when no collision shape is specified (or if collision shape is null). + * @param isCollidable Whether this is collidable or not. True by default. + * @param isStatic Whether this is static or not. False by default + */ + addPhysics(collisionShape?: Shape, colliderOffset?: Vec2, isCollidable: boolean = true, isStatic: boolean = false): void { + // Initialize the physics variables + this.hasPhysics = true; + this.moving = false; + this.onGround = false; + this.onWall = false; + this.onCeiling = false; + this.active = true; + this.isCollidable = isCollidable; + this.isStatic = isStatic; + this.isTrigger = false; + this.triggerMask = 0; + this.triggerEnters = new Array(32); + this.triggerExits = new Array(32); + this._velocity = Vec2.ZERO; + this.sweptRect = new AABB(); + this.collidedWithTilemap = false; + this.group = -1; // The default group, collides with everything + + // Set the collision shape if provided, or simply use the the region if there is one. + if(collisionShape){ + this.collisionShape = collisionShape; + this.collisionShape.center = this.position; + } else if (isRegion(this)) { + // If the gamenode has a region and no other is specified, use that + this.collisionShape = (this).boundary.clone(); + } else { + throw "No collision shape specified for physics object." + } + + // If we were provided with a collider offset, set it. Otherwise there is no offset, so use the zero vector + if(colliderOffset){ + this.colliderOffset = colliderOffset; + } else { + this.colliderOffset = Vec2.ZERO; + } + + // Initialize the swept rect + this.sweptRect = this.collisionShape.getBoundingRect(); + + // Register the object with physics + this.scene.getPhysicsManager().registerObject(this); + } + + /** Removes this object from the physics system */ + removePhysics(): void { + // Remove this from the physics manager + this.scene.getPhysicsManager().deregisterObject(this); + + // Nullify all physics fields + this.hasPhysics = false; + this.moving = false; + this.onGround = false; + this.onWall = false; + this.onCeiling = false; + this.active = false; + this.isCollidable = false; + this.isStatic = false; + this.isTrigger = false; + this.triggerMask = 0; + this.triggerEnters = null; + this.triggerExits = null; + this._velocity = Vec2.ZERO; + this.sweptRect = null; + this.collidedWithTilemap = false; + this.group = -1; + this.collisionShape = null; + this.colliderOffset = Vec2.ZERO; + this.sweptRect = null; + } + + /** Disables physics movement for this node */ + freeze(): void { + this.frozen = true; + } + + /** Reenables physics movement for this node */ + unfreeze(): void { + this.frozen = false; + } + + /** Prevents this object from participating in all collisions and triggers. It can still move. */ + disablePhysics(): void { + this.active = false; + } + + /** Enables this object to participate in collisions and triggers. This is only necessary if disablePhysics was called */ + enablePhysics(): void { + this.active = true; + } + + /** + * Sets the collider for this GameNode + * @param collider The new collider to use + */ + setCollisionShape(collider: Shape): void { + this.collisionShape = collider; + this.collisionShape.center.copy(this.position); + } + + // @implemented + /** + * Sets this object to be a trigger for a specific group + * @param group The name of the group that activates the trigger + * @param onEnter The name of the event to send when this trigger is activated + * @param onExit The name of the event to send when this trigger stops being activated + */ + setTrigger(group: string, onEnter: string, onExit: string): void { + // Make this object a trigger + this.isTrigger = true; + + // Get the number of the physics layer + let layerNumber = this.scene.getPhysicsManager().getGroupNumber(group); + + if(layerNumber === 0){ + console.warn(`Trigger for GameNode ${this.id} not set - group "${group}" was not recognized by the physics manager.`); + return; + } + + // Add this to the trigger mask + this.triggerMask |= layerNumber; + + // Layer numbers are bits, so get which bit it is + let index = Math.log2(layerNumber); + + // Set the event names + this.triggerEnters[index] = onEnter; + this.triggerExits[index] = onExit; + }; + + // @implemented + /** + * @param group The physics group this node should belong to + */ + setGroup(group: string): void { + this.scene.getPhysicsManager().setGroup(this, group); + } + + // @implemened + getLastVelocity(): Vec2 { + return this._velocity; + } + + /*---------- ACTOR ----------*/ + get ai(): AI { + return this._ai; + } + + set ai(ai: AI) { + if(!this._ai){ + // If we haven't been previously had an ai, register us with the ai manager + this.scene.getAIManager().registerActor(this); + } + + this._ai = ai; + this.aiActive = true; + } + + // @implemented + addAI(ai: string | (new () => T), options?: Record): void { + if(!this._ai){ + this.scene.getAIManager().registerActor(this); + } + + if(typeof ai === "string"){ + this._ai = this.scene.getAIManager().generateAI(ai); + } else { + this._ai = new ai(); + } + + this._ai.initializeAI(this, options); + + this.aiActive = true; + } + + // @implemented + setAIActive(active: boolean, options: Record): void { + this.aiActive = active; + if(this.aiActive){ + this.ai.activate(options); + } + } + + /*---------- TWEENABLE PROPERTIES ----------*/ + set positionX(value: number) { + this.position.x = value; + } + + set positionY(value: number) { + this.position.y = value; + } + + abstract set scaleX(value: number); + + abstract set scaleY(value: number); + + /*---------- GAME NODE ----------*/ + /** + * Sets the scene for this object. + * @param scene The scene this object belongs to. + */ + setScene(scene: Scene): void { + this.scene = scene; + } + + /** + * Gets the scene this object is in. + * @returns The scene this object belongs to + */ + getScene(): Scene { + return this.scene; + } + + /** + * Sets the layer of this object. + * @param layer The layer this object will be on. + */ + setLayer(layer: Layer): void { + this.layer = layer; + } + + /** + * Returns the layer this object is on. + * @returns This layer this object is on. + */ + getLayer(): Layer { + return this.layer; + } + + /** Called if the position vector is modified or replaced */ + protected positionChanged(): void { + if(this.collisionShape){ + if(this.colliderOffset){ + this.collisionShape.center = this.position.clone().add(this.colliderOffset); + } else { + this.collisionShape.center = this.position.clone(); + } + + } + }; + + /** + * Updates this GameNode + * @param deltaT The timestep of the update. + */ + update(deltaT: number): void { + // Defer event handling to AI. + while(this.receiver.hasNextEvent()){ + this._ai.handleEvent(this.receiver.getNextEvent()); + } + } + + // @implemented + debugRender(): void { + // Draw the position of this GameNode + Debug.drawPoint(this.relativePosition, Color.BLUE); + + // If velocity is not zero, draw a vector for it + if(this._velocity && !this._velocity.isZero()){ + Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), Color.BLUE); + } + + // If this has a collider, draw it + if(this.collisionShape){ + let color = this.isColliding ? Color.RED : Color.GREEN; + + if(this.isTrigger){ + color = Color.MAGENTA; + } + + color.a = 0.2; + + if(this.collisionShape instanceof AABB){ + Debug.drawBox(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.halfSize.scaled(this.scene.getViewScale()), true, color); + } else if(this.collisionShape instanceof Circle){ + Debug.drawCircle(this.inRelativeCoordinates(this.collisionShape.center), this.collisionShape.hw*this.scene.getViewScale(), true, color); + } + } + } +} + +export enum TweenableProperties{ + posX = "positionX", + posY = "positionY", + scaleX = "scaleX", + scaleY = "scaleY", + rotation = "rotation", + alpha = "alpha" +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Graphic.ts b/hw3/src/Wolfie2D/Nodes/Graphic.ts new file mode 100644 index 0000000..79383ad --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Graphic.ts @@ -0,0 +1,32 @@ +import CanvasNode from "./CanvasNode"; +import Color from "../Utils/Color"; + +/** + * The representation of a game object that doesn't rely on any resources to render - it is drawn to the screen by the canvas + */ +export default abstract class Graphic extends CanvasNode { + /** The color of the Graphic */ + color: Color; + + constructor(){ + super(); + this.color = Color.RED; + } + + get alpha(): number { + return this.color.a; + } + + set alpha(a: number) { + this.color.a = a; + } + + // @deprecated + /** + * Sets the color of the Graphic. DEPRECATED + * @param color The new color of the Graphic. + */ + setColor(color: Color){ + this.color = color; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Graphics/GraphicTypes.ts b/hw3/src/Wolfie2D/Nodes/Graphics/GraphicTypes.ts new file mode 100644 index 0000000..3631c46 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Graphics/GraphicTypes.ts @@ -0,0 +1,5 @@ +export enum GraphicType { + POINT = "POINT", + RECT = "RECT", + LINE = "LINE", +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Graphics/Line.ts b/hw3/src/Wolfie2D/Nodes/Graphics/Line.ts new file mode 100644 index 0000000..b6242ee --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Graphics/Line.ts @@ -0,0 +1,33 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Graphic from "../Graphic"; + +export default class Line extends Graphic { + protected _end: Vec2; + thickness: number; + + constructor(start: Vec2, end: Vec2){ + super(); + this.start = start; + this.end = end; + this.thickness = 2; + + // Does this really have a meaning for lines? + this.size.set(5, 5); + } + + set start(pos: Vec2){ + this.position = pos; + } + + get start(): Vec2 { + return this.position; + } + + set end(pos: Vec2){ + this._end = pos; + } + + get end(): Vec2 { + return this._end; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Graphics/Point.ts b/hw3/src/Wolfie2D/Nodes/Graphics/Point.ts new file mode 100644 index 0000000..0f75c5a --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Graphics/Point.ts @@ -0,0 +1,12 @@ +import Graphic from "../Graphic"; +import Vec2 from "../../DataTypes/Vec2"; + +/** A basic point to be drawn on the screen. */ +export default class Point extends Graphic { + + constructor(position: Vec2){ + super(); + this.position = position; + this.size.set(5, 5); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Graphics/Rect.ts b/hw3/src/Wolfie2D/Nodes/Graphics/Rect.ts new file mode 100644 index 0000000..7bac72a --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Graphics/Rect.ts @@ -0,0 +1,46 @@ +import Graphic from "../Graphic"; +import Vec2 from "../../DataTypes/Vec2"; +import Color from "../../Utils/Color"; + +/** A basic rectangle to be drawn on the screen. */ +export default class Rect extends Graphic { + + /** The border color of the Rect */ + borderColor: Color; + + /** The width of the border */ + borderWidth: number; + + constructor(position: Vec2, size: Vec2){ + super(); + this.position = position; + this.size = size; + this.borderColor = Color.TRANSPARENT; + this.borderWidth = 0; + } + + /** + * Sets the border color of this rectangle + * @param color The border color + */ + setBorderColor(color: Color): void { + this.borderColor = color; + } + + // @deprecated + getBorderColor(): Color { + return this.borderColor; + } + + /** + * Sets the border width of this rectangle + * @param width The width of the rectangle in pixels + */ + setBorderWidth(width: number){ + this.borderWidth = width; + } + + getBorderWidth(): number { + return this.borderWidth; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Sprites/AnimatedSprite.ts b/hw3/src/Wolfie2D/Nodes/Sprites/AnimatedSprite.ts new file mode 100644 index 0000000..bdd909f --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Sprites/AnimatedSprite.ts @@ -0,0 +1,49 @@ +import Sprite from "./Sprite"; +import AnimationManager from "../../Rendering/Animations/AnimationManager"; +import Spritesheet from "../../DataTypes/Spritesheet"; +import Vec2 from "../../DataTypes/Vec2"; + +/** An sprite with specified animation frames. */ +export default class AnimatedSprite extends Sprite { + /** The number of columns in this sprite sheet */ + protected numCols: number; + + get cols(): number { + return this.numCols; + } + + /** The number of rows in this sprite sheet */ + protected numRows: number; + + get rows(): number { + return this.numRows; + } + + /** The animationManager for this sprite */ + animation: AnimationManager; + + constructor(spritesheet: Spritesheet){ + super(spritesheet.name); + this.numCols = spritesheet.columns; + this.numRows = spritesheet.rows; + + // Set the size of the sprite to the sprite size specified by the spritesheet + this.size.set(spritesheet.spriteWidth, spritesheet.spriteHeight); + + this.animation = new AnimationManager(this); + + // Add the animations to the animated sprite + for(let animation of spritesheet.animations){ + this.animation.add(animation.name, animation); + } + } + + /** + * Gets the image offset for the current index of animation + * @param index The index we're at in the animation + * @returns A Vec2 containing the image offset + */ + getAnimationOffset(index: number): Vec2 { + return new Vec2((index % this.numCols) * this.size.x, Math.floor(index / this.numCols) * this.size.y); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Sprites/Sprite.ts b/hw3/src/Wolfie2D/Nodes/Sprites/Sprite.ts new file mode 100644 index 0000000..4184e1b --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Sprites/Sprite.ts @@ -0,0 +1,35 @@ +import CanvasNode from "../CanvasNode"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Vec2 from "../../DataTypes/Vec2"; + +/** + * The representation of a sprite - an in-game image + */ +export default class Sprite extends CanvasNode { + /** The id of the image from the resourceManager */ + imageId: string; + /** The offset of the sprite in an atlas image */ + imageOffset: Vec2; + /** Whether or not the x-axis should be inverted on render */ + invertX: boolean; + /** Whether or not the y-axis should be inverted on render */ + invertY: boolean; + + constructor(imageId: string){ + super(); + this.imageId = imageId; + let image = ResourceManager.getInstance().getImage(this.imageId); + this.size = new Vec2(image.width, image.height); + this.imageOffset = Vec2.ZERO; + this.invertX = false; + this.invertY = false; + } + + /** + * Sets the offset of the sprite from (0, 0) in the image's coordinates + * @param offset The offset of the sprite from (0, 0) in image coordinates + */ + setImageOffset(offset: Vec2): void { + this.imageOffset = offset; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Tilemap.ts b/hw3/src/Wolfie2D/Nodes/Tilemap.ts new file mode 100644 index 0000000..4f821f4 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Tilemap.ts @@ -0,0 +1,119 @@ +import Vec2 from "../DataTypes/Vec2"; +import Tileset from "../DataTypes/Tilesets/Tileset"; +import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData" +import CanvasNode from "./CanvasNode"; +import PhysicsManager from "../Physics/PhysicsManager"; + +/** + * The representation of a tilemap - this can consist of a combination of tilesets in one layer + */ +export default abstract class Tilemap extends CanvasNode { + /** An array of the tilesets that this tilemap uses */ + protected tilesets: Array; + + /** The size of a tile in this tilemap */ + protected tileSize: Vec2; + + /** An array of tile data */ + protected data: Array; + + /** An array of tile collision data */ + protected collisionMap: Array; + + /** The name of the tilemap */ + name: string; + + // TODO: Make this no longer be specific to Tiled + constructor(tilemapData: TiledTilemapData, layer: TiledLayerData, tilesets: Array, scale: Vec2) { + super(); + this.tilesets = tilesets; + this.tileSize = new Vec2(0, 0); + this.name = layer.name; + + let tilecount = 0; + for(let tileset of tilesets){ + tilecount += tileset.getTileCount() + 1; + } + + this.collisionMap = new Array(tilecount); + for(let i = 0; i < this.collisionMap.length; i++){ + this.collisionMap[i] = false; + } + + // Defer parsing of the data to child classes - this allows for isometric vs. orthographic tilemaps and handling of Tiled data or other data + this.parseTilemapData(tilemapData, layer); + this.scale.set(scale.x, scale.y); + } + + /** + * Returns an array of the tilesets associated with this tilemap + * @returns An array of all of the tilesets assocaited with this tilemap. + */ + getTilesets(): Tileset[] { + return this.tilesets; + } + + /** + * Returns the size of tiles in this tilemap as they appear in the game world after scaling + * @returns A vector containing the size of tiles in this tilemap as they appear in the game world after scaling. + */ + getTileSize(): Vec2 { + return this.tileSize.scaled(this.scale.x, this.scale.y); + } + + /** + * Gets the tile size taking zoom into account + * @returns The tile size with zoom + */ + getTileSizeWithZoom(): Vec2 { + let zoom = this.scene.getViewScale(); + + return this.getTileSize().scale(zoom); + } + + /** + * Adds this tilemap to the physics system + */ + addPhysics(): void { + this.hasPhysics = true; + this.active = true; + this.group = -1; + this.scene.getPhysicsManager().registerTilemap(this); + } + + /** + * Returns the value of the tile at the specified position + * @param worldCoords The position in world coordinates + * @returns A number that represents the data value of the tile at the specified world position. + */ + abstract getTileAtWorldPosition(worldCoords: Vec2): number; + + /** + * Returns the world position of the top left corner of the tile at the specified index + * @param index The index of the tile in the tileData array + * @returns The world position of the tile at the specified index + */ + abstract getTileWorldPosition(index: number): Vec2; + + /** + * Returns the value of the tile at the specified index + * @param index The index of the tile in the tileData array + * @returns The value of the tile in the tileData array + */ + abstract getTile(index: number): number; + + /** + * Sets the tile at the specified index + * @param index The index of the tile + * @param type The new data value of the tile + */ + abstract setTile(index: number, type: number): void; + + // TODO: This shouldn't use tiled data specifically - it should be more general + /** + * Sets up the tileset using the data loaded from file + * @param tilemapData The tilemap data from file + * @param layer The layer data from file + */ + protected abstract parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/Tilemaps/OrthogonalTilemap.ts b/hw3/src/Wolfie2D/Nodes/Tilemaps/OrthogonalTilemap.ts new file mode 100644 index 0000000..9b9703e --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/Tilemaps/OrthogonalTilemap.ts @@ -0,0 +1,190 @@ +import Tilemap from "../Tilemap"; +import Vec2 from "../../DataTypes/Vec2"; +import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData"; +import Debug from "../../Debug/Debug"; +import Color from "../../Utils/Color"; + +/** + * The representation of an orthogonal tilemap - i.e. a top down or platformer tilemap + */ +export default class OrthogonalTilemap extends Tilemap { + /** The number of columns in the tilemap */ + protected numCols: number; + /** The number of rows in the tilemap */ + protected numRows: number; + + // @override + protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void { + // The size of the tilemap in local space + this.numCols = tilemapData.width; + this.numRows = tilemapData.height; + + // The size of tiles + this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight); + + // The size of the tilemap on the canvas + this.size.set(this.numCols * this.tileSize.x, this.numRows * this.tileSize.y); + this.position.copy(this.size.scaled(0.5)); + this.data = layer.data; + this.visible = layer.visible; + + // Whether the tilemap is collidable or not + this.isCollidable = false; + if(layer.properties){ + for(let item of layer.properties){ + if(item.name === "Collidable"){ + this.isCollidable = item.value; + + // Set all tiles besides "empty: 0" to be collidable + for(let i = 1; i < this.collisionMap.length; i++){ + this.collisionMap[i] = true; + } + } + } + } + } + + /** + * Gets the dimensions of the tilemap + * @returns A Vec2 containing the number of columns and the number of rows in the tilemap. + */ + getDimensions(): Vec2 { + return new Vec2(this.numCols, this.numRows); + } + + /** + * Gets the data value of the tile at the specified world position + * @param worldCoords The coordinates in world space + * @returns The data value of the tile + */ + getTileAtWorldPosition(worldCoords: Vec2): number { + let localCoords = this.getColRowAt(worldCoords); + return this.getTileAtRowCol(localCoords); + } + + /** + * Get the tile at the specified row and column + * @param rowCol The coordinates in tilemap space + * @returns The data value of the tile + */ + getTileAtRowCol(rowCol: Vec2): number { + if(rowCol.x < 0 || rowCol.x >= this.numCols || rowCol.y < 0 || rowCol.y >= this.numRows){ + return -1; + } + + return this.data[rowCol.y * this.numCols + rowCol.x]; + } + + /** + * Gets the world position of the tile at the specified index + * @param index The index of the tile + * @returns A Vec2 containing the world position of the tile + */ + getTileWorldPosition(index: number): Vec2 { + // Get the local position + let col = index % this.numCols; + let row = Math.floor(index / this.numCols); + + // Get the world position + let x = col * this.tileSize.x; + let y = row * this.tileSize.y; + + return new Vec2(x, y); + } + + /** + * Gets the data value of the tile at the specified index + * @param index The index of the tile + * @returns The data value of the tile + */ + getTile(index: number): number { + return this.data[index]; + } + + // @override + setTile(index: number, type: number): void { + this.data[index] = type; + } + + /** + * Sets the tile at the specified row and column + * @param rowCol The position of the tile in tilemap space + * @param type The new data value of the tile + */ + setTileAtRowCol(rowCol: Vec2, type: number): void { + let index = rowCol.y * this.numCols + rowCol.x; + this.setTile(index, type); + } + + /** + * Returns true if the tile at the specified row and column of the tilemap is collidable + * @param indexOrCol The index of the tile or the column it is in + * @param row The row the tile is in + * @returns A flag representing whether or not the tile is collidable. + */ + isTileCollidable(indexOrCol: number, row?: number): boolean { + // The value of the tile + let tile = 0; + + if(row){ + // We have a column and a row + tile = this.getTileAtRowCol(new Vec2(indexOrCol, row)); + + if(tile < 0){ + return false; + } + } else { + if(indexOrCol < 0 || indexOrCol >= this.data.length){ + // Tiles that don't exist aren't collidable + return false; + } + // We have an index + tile = this.getTile(indexOrCol); + } + + return this.collisionMap[tile]; + } + + /** + * Takes in world coordinates and returns the row and column of the tile at that position + * @param worldCoords The coordinates of the potential tile in world space + * @returns A Vec2 containing the coordinates of the potential tile in tilemap space + */ + getColRowAt(worldCoords: Vec2): Vec2 { + let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x); + let row = Math.floor(worldCoords.y / this.tileSize.y / this.scale.y); + + return new Vec2(col, row); + } + + // @override + update(deltaT: number): void {} + + // @override + debugRender(){ + // Half of the tile size + let zoomedHalfTileSize = this.getTileSizeWithZoom().scaled(0.5); + let halfTileSize = this.getTileSize().scaled(0.5); + + // The center of the top left tile + let topLeft = this.position.clone().sub(this.size.scaled(0.5)); + + // A vec to store the center + let center = Vec2.ZERO; + + for(let col = 0; col < this.numCols; col++){ + // Calculate the x-position + center.x = topLeft.x + col*2*halfTileSize.x + halfTileSize.x; + + for(let row = 0; row < this.numRows; row++){ + if(this.isCollidable && this.isTileCollidable(col, row)){ + // Calculate the y-position + center.y = topLeft.y + row*2*halfTileSize.y + halfTileSize.y; + + // Draw a box for this tile + Debug.drawBox(this.inRelativeCoordinates(center), zoomedHalfTileSize, false, Color.BLUE); + } + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElement.ts b/hw3/src/Wolfie2D/Nodes/UIElement.ts new file mode 100644 index 0000000..f6cb36b --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElement.ts @@ -0,0 +1,149 @@ +import CanvasNode from "./CanvasNode"; +import Color from "../Utils/Color"; +import Vec2 from "../DataTypes/Vec2"; +import Input from "../Input/Input"; + +/** + * The representation of a UIElement - the parent class of things like buttons + */ +export default abstract class UIElement extends CanvasNode { + // Style attributes - TODO - abstract this into a style object/interface + /** The backgound color */ + backgroundColor: Color; + /** The border color */ + borderColor: Color; + /** The border radius */ + borderRadius: number; + /** The border width */ + borderWidth: number; + /** The padding */ + padding: Vec2; + + // EventAttributes + /** The reaction of this UIElement on a click */ + onClick: Function; + /** The event propagated on click */ + onClickEventId: string; + /** The reaction to the release of a click */ + onRelease: Function; + /** The event propagated on the release of a click */ + onReleaseEventId: string; + /** The reaction when a mouse enters this UIElement */ + onEnter: Function; + /** The event propagated when a mouse enters this UIElement */ + onEnterEventId: string; + /** The reaction when a mouse leaves this UIElement */ + onLeave: Function; + /** The event propogated when a mouse leaves this UIElement */ + onLeaveEventId: string; + + /** Whether or not this UIElement is currently clicked on */ + protected isClicked: boolean; + /** Whether or not this UIElement is currently hovered over */ + protected isEntered: boolean; + + constructor(position: Vec2){ + super(); + this.position = position; + + this.backgroundColor = new Color(0, 0, 0, 0); + this.borderColor = new Color(0, 0, 0, 0); + this.borderRadius = 5; + this.borderWidth = 1; + this.padding = Vec2.ZERO; + + this.onClick = null; + this.onClickEventId = null; + this.onRelease = null; + this.onReleaseEventId = null; + + this.onEnter = null; + this.onEnterEventId = null; + this.onLeave = null; + this.onLeaveEventId = null; + + this.isClicked = false; + this.isEntered = false; + } + + // @deprecated + setBackgroundColor(color: Color): void { + this.backgroundColor = color; + } + + // @deprecated + setPadding(padding: Vec2): void { + this.padding.copy(padding); + } + + update(deltaT: number): void { + super.update(deltaT); + + // See of this object was just clicked + if(Input.isMouseJustPressed()){ + let clickPos = Input.getMousePressPosition(); + if(this.contains(clickPos.x, clickPos.y) && this.visible && !this.layer.isHidden()){ + this.isClicked = true; + + if(this.onClick !== null){ + this.onClick(); + } + if(this.onClickEventId !== null){ + let data = {}; + this.emitter.fireEvent(this.onClickEventId, data); + } + } + } + + // If the mouse wasn't just pressed, then we definitely weren't clicked + if(!Input.isMousePressed()){ + if(this.isClicked){ + this.isClicked = false; + } + } + + // Check if the mouse is hovering over this element + let mousePos = Input.getMousePosition(); + if(mousePos && this.contains(mousePos.x, mousePos.y)){ + this.isEntered = true; + + if(this.onEnter !== null){ + this.onEnter(); + } + if(this.onEnterEventId !== null){ + let data = {}; + this.emitter.fireEvent(this.onEnterEventId, data); + } + + } else if(this.isEntered) { + this.isEntered = false; + + if(this.onLeave !== null){ + this.onLeave(); + } + if(this.onLeaveEventId !== null){ + let data = {}; + this.emitter.fireEvent(this.onLeaveEventId, data); + } + } else if(this.isClicked) { + // If mouse is dragged off of element while down, it is not clicked anymore + this.isClicked = false; + } + } + + /** + * Overridable method for calculating background color - useful for elements that want to be colored on different after certain events + * @returns The background color of the UIElement + */ + calculateBackgroundColor(): Color { + return this.backgroundColor; + } + + /** + * Overridable method for calculating border color - useful for elements that want to be colored on different after certain events + * @returns The border color of the UIElement + */ + calculateBorderColor(): Color { + return this.borderColor; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElements/Button.ts b/hw3/src/Wolfie2D/Nodes/UIElements/Button.ts new file mode 100644 index 0000000..8953854 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElements/Button.ts @@ -0,0 +1,27 @@ +import Label from "./Label"; +import Color from "../../Utils/Color"; +import Vec2 from "../../DataTypes/Vec2"; + +/** A clickable button UIElement */ +export default class Button extends Label { + + constructor(position: Vec2, text: string){ + super(position, text); + + this.backgroundColor = new Color(150, 75, 203); + this.borderColor = new Color(41, 46, 30); + this.textColor = new Color(255, 255, 255); + } + + // @override + calculateBackgroundColor(): Color { + // Change the background color if clicked or hovered + if(this.isEntered && !this.isClicked){ + return this.backgroundColor.lighten(); + } else if(this.isClicked){ + return this.backgroundColor.darken(); + } else { + return this.backgroundColor; + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElements/Label.ts b/hw3/src/Wolfie2D/Nodes/UIElements/Label.ts new file mode 100644 index 0000000..e89eb80 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElements/Label.ts @@ -0,0 +1,152 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Color from "../../Utils/Color"; +import UIElement from "../UIElement"; + +/** A basic text-containing label */ +export default class Label extends UIElement{ + /** The color of the text of this UIElement */ + textColor: Color; + /** The value of the text of this UIElement */ + text: string; + /** The name of the font */ + font: string; + /** The size of the font */ + fontSize: number; + /** The horizontal alignment of the text within the label */ + protected hAlign: string; + /** The vertical alignment of text within the label */ + protected vAlign: string; + + /** A flag for if the width of the text has been measured on the canvas for auto width assignment */ + protected sizeAssigned: boolean; + + constructor(position: Vec2, text: string){ + super(position); + this.text = text; + this.textColor = new Color(0, 0, 0, 1); + this.font = "Arial"; + this.fontSize = 30; + this.hAlign = "center"; + this.vAlign = "center"; + + this.sizeAssigned = false; + } + + // @deprecated + setText(text: string): void { + this.text = text; + } + + // @deprecated + setTextColor(color: Color): void { + this.textColor = color; + } + + /** + * Gets a string containg the font details for rendering + * @returns A string containing the font details + */ + getFontString(): string { + return this.fontSize + "px " + this.font; + } + + /** + * Overridable method for calculating text color - useful for elements that want to be colored on different after certain events + * @returns a string containg the text color + */ + calculateTextColor(): string { + return this.textColor.toStringRGBA(); + } + + /** + * Uses the canvas to calculate the width of the text + * @param ctx The rendering context + * @returns A number representing the rendered text width + */ + protected calculateTextWidth(ctx: CanvasRenderingContext2D): number { + ctx.font = this.fontSize + "px " + this.font; + return ctx.measureText(this.text).width; + } + + setHAlign(align: string): void { + this.hAlign = align; + } + + setVAlign(align: string): void { + this.vAlign = align; + } + + /** + * Calculate the offset of the text - this is used for rendering text with different alignments + * @param ctx The rendering context + * @returns The offset of the text in a Vec2 + */ + calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 { + let textWidth = this.calculateTextWidth(ctx); + + let offset = new Vec2(0, 0); + + let hDiff = this.size.x - textWidth; + if(this.hAlign === HAlign.CENTER){ + offset.x = hDiff/2; + } else if (this.hAlign === HAlign.RIGHT){ + offset.x = hDiff; + } + + if(this.vAlign === VAlign.TOP){ + ctx.textBaseline = "top"; + offset.y = 0; + } else if (this.vAlign === VAlign.BOTTOM){ + ctx.textBaseline = "bottom"; + offset.y = this.size.y; + } else { + ctx.textBaseline = "middle"; + offset.y = this.size.y/2; + } + + return offset; + } + + protected sizeChanged(): void { + super.sizeChanged(); + this.sizeAssigned = true; + } + + /** + * Automatically sizes the element to the text within it + * @param ctx The rendering context + */ + protected autoSize(ctx: CanvasRenderingContext2D): void { + let width = this.calculateTextWidth(ctx); + let height = this.fontSize; + this.size.set(width + this.padding.x*2, height + this.padding.y*2); + this.sizeAssigned = true; + } + + /** + * Initially assigns a size to the UIElement if none is provided + * @param ctx The rendering context + */ + handleInitialSizing(ctx: CanvasRenderingContext2D): void { + if(!this.sizeAssigned){ + this.autoSize(ctx); + } + } + + /** On the next render, size this element to it's current text using its current font size */ + sizeToText(): void { + this.sizeAssigned = false; + } +} + +export enum VAlign { + TOP = "top", + CENTER = "center", + BOTTOM = "bottom" +} + +export enum HAlign { + LEFT = "left", + CENTER = "center", + RIGHT = "right" +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElements/Slider.ts b/hw3/src/Wolfie2D/Nodes/UIElements/Slider.ts new file mode 100644 index 0000000..f9a88b4 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElements/Slider.ts @@ -0,0 +1,64 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Input from "../../Input/Input"; +import Color from "../../Utils/Color"; +import MathUtils from "../../Utils/MathUtils"; +import UIElement from "../UIElement"; + +/** A slider UIElement */ +export default class Slider extends UIElement { + /** The value of the slider from [0, 1] */ + protected value: number; + /** The color of the slider nib */ + public nibColor: Color; + /** The size of the nib */ + public nibSize: Vec2; + /** The color of the slider track */ + public sliderColor: Color; + /** The reaction of this UIElement to a value change */ + public onValueChange: Function; + /** The event propagated by this UIElement when value changes */ + public onValueChangeEventId: string; + + constructor(position: Vec2, initValue: number){ + super(position); + + this.value = initValue; + this.nibColor = Color.RED; + this.sliderColor = Color.BLACK; + this.backgroundColor = Color.TRANSPARENT; + this.borderColor = Color.TRANSPARENT; + this.nibSize = new Vec2(10, 20); + + // Set a default size + this.size.set(200, 20); + } + + /** + * Retrieves the value of the slider + * @returns The value of the slider + */ + getValue(): number { + return this.value; + } + + /** A method called in response to the value changing */ + protected valueChanged(): void { + if(this.onValueChange){ + this.onValueChange(this.value); + } + + if(this.onValueChangeEventId){ + this.emitter.fireEvent(this.onValueChangeEventId, {target: this, value: this.value}); + } + } + + update(deltaT: number): void { + super.update(deltaT); + + if(this.isClicked){ + let val = MathUtils.invLerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, Input.getMousePosition().x); + this.value = MathUtils.clamp01(val); + this.valueChanged(); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElements/TextInput.ts b/hw3/src/Wolfie2D/Nodes/UIElements/TextInput.ts new file mode 100644 index 0000000..0bae845 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElements/TextInput.ts @@ -0,0 +1,64 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Color from "../../Utils/Color"; +import Label from "./Label"; +import Input from "../../Input/Input"; + +/** A text input UIElement */ +export default class TextInput extends Label { + /** A flag the represents whether the user can type in this TextInput */ + focused: boolean; + /** The position of the cursor in this TextInput */ + cursorCounter: number; + + constructor(position: Vec2){ + super(position, ""); + + this.focused = false; + this.cursorCounter = 0; + + // Give a default size to the x only + this.size.set(200, this.fontSize); + this.hAlign = "left"; + + this.borderColor = Color.BLACK; + this.backgroundColor = Color.WHITE; + } + + update(deltaT: number): void { + super.update(deltaT); + + if(Input.isMouseJustPressed()){ + let clickPos = Input.getMousePressPosition(); + if(this.contains(clickPos.x, clickPos.y)){ + this.focused = true; + this.cursorCounter = 30; + } else { + this.focused = false; + } + } + + if(this.focused){ + let keys = Input.getKeysJustPressed(); + let nums = "1234567890"; + let specialChars = "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"; + let letters = "qwertyuiopasdfghjklzxcvbnm"; + let mask = nums + specialChars + letters; + keys = keys.filter(key => mask.includes(key)); + let shiftPressed = Input.isKeyPressed("shift"); + let backspacePressed = Input.isKeyJustPressed("backspace"); + let spacePressed = Input.isKeyJustPressed("space"); + + if(backspacePressed){ + this.text = this.text.substring(0, this.text.length - 1); + } else if(spacePressed){ + this.text += " "; + } else if(keys.length > 0) { + if(shiftPressed){ + this.text += keys[0].toUpperCase(); + } else { + this.text += keys[0]; + } + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Nodes/UIElements/UIElementTypes.ts b/hw3/src/Wolfie2D/Nodes/UIElements/UIElementTypes.ts new file mode 100644 index 0000000..6530db2 --- /dev/null +++ b/hw3/src/Wolfie2D/Nodes/UIElements/UIElementTypes.ts @@ -0,0 +1,6 @@ +export enum UIElementType { + BUTTON = "BUTTON", + LABEL = "LABEL", + SLIDER = "SLIDER", + TEXT_INPUT = "TEXTINPUT" +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Pathfinding/NavigationManager.ts b/hw3/src/Wolfie2D/Pathfinding/NavigationManager.ts new file mode 100644 index 0000000..1daf89d --- /dev/null +++ b/hw3/src/Wolfie2D/Pathfinding/NavigationManager.ts @@ -0,0 +1,39 @@ +import Navigable from "../DataTypes/Interfaces/Navigable"; +import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import NavigationPath from "./NavigationPath"; + +/** + * The manager class for navigation. + * Handles all navigable entities, such and allows them to be accessed by outside systems by requesting a path + * from one position to another. + */ +export default class NavigationManager { + /** The list of all navigable entities */ + protected navigableEntities: Map; + + constructor(){ + this.navigableEntities = new Map(); + } + + /** + * Adds a navigable entity to the NavigationManager + * @param navName The name of the navigable entitry + * @param nav The actual Navigable instance + */ + addNavigableEntity(navName: string, nav: Navigable): void { + this.navigableEntities.add(navName, nav); + } + + /** + * Gets a path frome one point to another using a specified Navigable object + * @param navName The name of the registered Navigable object + * @param fromPosition The starting position of navigation + * @param toPosition The ending position of Navigation + * @returns A NavigationPath containing the route to take over the Navigable entity to get between the provided positions. + */ + getPath(navName: string, fromPosition: Vec2, toPosition: Vec2): NavigationPath { + let nav = this.navigableEntities.get(navName); + return nav.getNavigationPath(fromPosition.clone(), toPosition.clone()); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Pathfinding/NavigationPath.ts b/hw3/src/Wolfie2D/Pathfinding/NavigationPath.ts new file mode 100644 index 0000000..52ff112 --- /dev/null +++ b/hw3/src/Wolfie2D/Pathfinding/NavigationPath.ts @@ -0,0 +1,58 @@ +import Stack from "../DataTypes/Stack"; +import Vec2 from "../DataTypes/Vec2"; +import GameNode from "../Nodes/GameNode"; + +/** + * A path that AIs can follow. Uses finishMove() in Physical to determine progress on the route + */ +export default class NavigationPath { + /** The navigation path, stored as a stack of next positions */ + protected path: Stack; + /** The current direction of movement */ + protected currentMoveDirection: Vec2; + /** The distance a node must be to a point to consider it as having arrived */ + protected distanceThreshold: number; + + /** + * Constructs a new NavigationPath + * @param path The path of nodes to take + */ + constructor(path: Stack){ + this.path = path; + this.currentMoveDirection = Vec2.ZERO; + this.distanceThreshold = 4; + } + + /** + * Returns the status of navigation along this NavigationPath + * @returns True if the node has reached the end of the path, false otherwise + */ + isDone(): boolean { + return this.path.isEmpty(); + } + + /** + * Gets the movement direction in the current position along the path + * @param node The node to move along the path + * @returns The movement direction as a Vec2 + */ + getMoveDirection(node: GameNode): Vec2 { + // Return direction to next point in the nav + return node.position.dirTo(this.path.peek()); + } + + /** + * Updates this NavigationPath to the current state of the GameNode + * @param node The node moving along the path + */ + handlePathProgress(node: GameNode): void { + if(node.position.distanceSqTo(this.path.peek()) < this.distanceThreshold*this.distanceThreshold){ + // We've reached our node, move on to the next destination + this.path.pop(); + } + } + + toString(): string { + return this.path.toString() + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Pathfinding/Navmesh.ts b/hw3/src/Wolfie2D/Pathfinding/Navmesh.ts new file mode 100644 index 0000000..4b62765 --- /dev/null +++ b/hw3/src/Wolfie2D/Pathfinding/Navmesh.ts @@ -0,0 +1,67 @@ +import PositionGraph from "../DataTypes/Graphs/PositionGraph"; +import Navigable from "../DataTypes/Interfaces/Navigable"; +import Stack from "../DataTypes/Stack"; +import Vec2 from "../DataTypes/Vec2"; +import GraphUtils from "../Utils/GraphUtils"; +import NavigationPath from "./NavigationPath"; + +/** + * An implementation of a Navmesh. Navmeshes are graphs in the game world along which nodes can move. + */ +export default class Navmesh implements Navigable { + /** The graph of points in the NavMesh */ + protected graph: PositionGraph; + + /** + * Creates a new Navmesh from the points in the speecified graph + * @param graph The graph to construct a navmesh from + */ + constructor(graph: PositionGraph){ + this.graph = graph; + } + + // @implemented + getNavigationPath(fromPosition: Vec2, toPosition: Vec2): NavigationPath { + let start = this.getClosestNode(fromPosition); + let end = this.getClosestNode(toPosition); + + let parent = GraphUtils.djikstra(this.graph, start); + + let pathStack = new Stack(this.graph.numVertices); + + // Push the final position and the final position in the graph + pathStack.push(toPosition.clone()); + pathStack.push(this.graph.positions[end]); + + // Add all parents along the path + let i = end; + while(parent[i] !== -1){ + pathStack.push(this.graph.positions[parent[i]]); + i = parent[i]; + } + + return new NavigationPath(pathStack); + } + + /** + * Gets the closest node in this Navmesh to the specified position + * @param position The position to query + * @returns The index of the closest node in the Navmesh to the position + */ + protected getClosestNode(position: Vec2): number { + let n = this.graph.numVertices; + let i = 1; + let index = 0; + let dist = position.distanceSqTo(this.graph.positions[0]); + while(i < n){ + let d = position.distanceSqTo(this.graph.positions[i]); + if(d < dist){ + dist = d; + index = i; + } + i++; + } + + return index; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Physics/BasicPhysicsManager.ts b/hw3/src/Wolfie2D/Physics/BasicPhysicsManager.ts new file mode 100644 index 0000000..0b5086a --- /dev/null +++ b/hw3/src/Wolfie2D/Physics/BasicPhysicsManager.ts @@ -0,0 +1,349 @@ +import GameNode from "../Nodes/GameNode"; +import Physical from "../DataTypes/Interfaces/Physical"; +import Tilemap from "../Nodes/Tilemap"; +import PhysicsManager from "./PhysicsManager"; +import Vec2 from "../DataTypes/Vec2"; +import AABB from "../DataTypes/Shapes/AABB"; +import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap"; +import AreaCollision from "../DataTypes/Physics/AreaCollision"; +import Unique from "../DataTypes/Interfaces/Unique"; + +/** + * ALGORITHM: + * In an effort to keep things simple and working effectively, each dynamic node will resolve its + * collisions considering the rest of the world as static. + * + * Collision detecting will happen first. This can be considered a broad phase, but it is not especially + * efficient, as it does not need to be for this game engine. Every dynamic node is checked against every + * other node for collision area. If collision area is non-zero (meaning the current node sweeps into another), + * it is added to a list of hits. + * + * INITIALIZATION: + * - Physics constants are reset + * - Swept shapes are recalculated. If a node isn't moving, it is skipped. + * + * COLLISION DETECTION: + * - For a node, collision area will be calculated using the swept AABB of the node against every other AABB in a static state + * - These collisions will be sorted by area in descending order + * + * COLLISION RESOLUTION: + * - For each hit, time of collision is calculated using a swept line through the AABB of the static node expanded + * with minkowski sums (discretely, but the concept is there) + * - The collision is resolved based on the near time of the collision (from method of separated axes) + * - X is resolved by near x, Y by near y. + * - There is some fudging to allow for sliding along walls of separate colliders. Sorting by area also helps with this. + * - Corner to corner collisions are resolve to favor x-movement. This is in consideration of platformers, to give + * the player some help with jumps + * + * Pros: + * - Everything happens with a consistent time. There is a distinct before and after for each resolution. + * - No back-tracking needs to be done. Once we resolve a node, it is definitively resolved. + * + * Cons: + * - Nodes that are processed early have movement priority over other nodes. This can lead to some undesirable interactions. + */ +export default class BasicPhysicsManager extends PhysicsManager { + + /** The array of static nodes */ + protected staticNodes: Array; + + /** The array of dynamic nodes */ + protected dynamicNodes: Array; + + /** The array of tilemaps */ + protected tilemaps: Array; + + /** An array of the collision masks for each group */ + protected collisionMasks: Array; + + constructor(options: Record){ + super(); + this.staticNodes = new Array(); + this.dynamicNodes = new Array(); + this.tilemaps = new Array(); + this.collisionMasks = new Array(32); + + // Parse options + this.parseOptions(options); + } + + /** + * Parses the options for constructing the physics manager + * @param options A record of options + */ + protected parseOptions(options: Record): void { + if(options.groupNames !== undefined && options.collisions !== undefined){ + for(let i = 0; i < options.groupNames.length; i++){ + let group = options.groupNames[i]; + + // Register the group name and number + this.groupNames[i] = group; + + this.groupMap.set(group, 1 << i); + + let collisionMask = 0; + + for(let j = 0; j < options.collisions[i].length; j++){ + if(options.collisions[i][j]){ + collisionMask |= 1 << j; + } + } + + this.collisionMasks[i] = collisionMask; + } + } + } + + // @override + registerObject(node: Physical): void { + if(node.isStatic){ + // Static and not collidable + this.staticNodes.push(node); + } else { + // Dynamic and not collidable + this.dynamicNodes.push(node); + } + } + + // @override + deregisterObject(node: Physical): void { + if(node.isStatic){ + // Remove the node from the static list + const index = this.staticNodes.indexOf(node); + this.staticNodes.splice(index, 1); + } else { + // Remove the node from the dynamic list + const index = this.dynamicNodes.indexOf(node); + this.dynamicNodes.splice(index, 1); + } + } + + // @override + registerTilemap(tilemap: Tilemap): void { + this.tilemaps.push(tilemap); + } + + // @override + deregisterTilemap(tilemap: Tilemap): void { + const index = this.tilemaps.indexOf(tilemap); + this.tilemaps.splice(index, 1); + } + + // @override + update(deltaT: number): void { + for(let node of this.dynamicNodes){ + /*---------- INITIALIZATION PHASE ----------*/ + // Clear frame dependent boolean values for each node + node.onGround = false; + node.onCeiling = false; + node.onWall = false; + node.collidedWithTilemap = false; + node.isColliding = false; + + // If this node is not active, don't process it + if(!node.active){ + continue; + } + + // Update the swept shapes of each node + if(node.moving){ + // If moving, reflect that in the swept shape + node.sweptRect.sweep(node._velocity, node.collisionShape.center, node.collisionShape.halfSize); + } else { + // If our node isn't moving, don't bother to check it (other nodes will detect if they run into it) + node._velocity.zero(); + continue; + } + + /*---------- DETECTION PHASE ----------*/ + // Gather a set of overlaps + let overlaps = new Array(); + + let groupIndex = node.group === -1 ? -1 : Math.log2(node.group); + + // First, check this node against every static node (order doesn't actually matter here, since we sort anyways) + for(let other of this.staticNodes){ + // Ignore inactive nodes + if(!other.active) continue; + + let collider = other.collisionShape.getBoundingRect(); + let area = node.sweptRect.overlapArea(collider); + if(area > 0){ + // We had a collision + overlaps.push(new AreaCollision(area, collider, other, "GameNode", null)); + } + } + + // Then, check it against every dynamic node + for(let other of this.dynamicNodes){ + // Ignore ourselves + if(node === other) continue; + + // Ignore inactive nodes + if(!other.active) continue; + + let collider = other.collisionShape.getBoundingRect(); + let area = node.sweptRect.overlapArea(collider); + if(area > 0){ + // We had a collision + overlaps.push(new AreaCollision(area, collider, other, "GameNode", null)); + } + } + + // Lastly, gather a set of AABBs from the tilemap. + // This step involves the most extra work, so it is abstracted into a method + for(let tilemap of this.tilemaps){ + // Ignore inactive tilemaps + if(!tilemap.active) continue; + + if(tilemap instanceof OrthogonalTilemap){ + this.collideWithOrthogonalTilemap(node, tilemap, overlaps); + } + } + + // Sort the overlaps by area + overlaps = overlaps.sort((a, b) => b.area - a.area); + + // Keep track of hits to use later + let hits = []; + + /*---------- RESOLUTION PHASE ----------*/ + // For every overlap, determine if we need to collide with it and when + for(let overlap of overlaps){ + // Ignore nodes we don't interact with + if( groupIndex !== -1 && overlap.other.group !== -1 && ((this.collisionMasks[groupIndex] & overlap.other.group) === 0) ) continue; + + // Do a swept line test on the static AABB with this AABB size as padding (this is basically using a minkowski sum!) + // Start the sweep at the position of this node with a delta of _velocity + const point = node.collisionShape.center; + const delta = node._velocity; + const padding = node.collisionShape.halfSize; + const otherAABB = overlap.collider; + + + const hit = otherAABB.intersectSegment(node.collisionShape.center, node._velocity, node.collisionShape.halfSize); + + overlap.hit = hit; + + if(hit !== null){ + hits.push(hit); + + // We got a hit, resolve with the time inside of the hit + let tnearx = hit.nearTimes.x; + let tneary = hit.nearTimes.y; + + // Allow edge clipping (edge overlaps don't count, only area overlaps) + // Importantly don't allow both cases to be true. Then we clip through corners. Favor x to help players land jumps + if(tnearx < 1.0 && (point.y === otherAABB.top - padding.y || point.y === otherAABB.bottom + padding.y) && delta.x !== 0) { + tnearx = 1.0; + } else if(tneary < 1.0 && (point.x === otherAABB.left - padding.x || point.x === otherAABB.right + padding.x) && delta.y !== 0) { + tneary = 1.0; + } + + + if(hit.nearTimes.x >= 0 && hit.nearTimes.x < 1){ + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + node._velocity.x = node._velocity.x * tnearx; + node.isColliding = true; + } + } + + if(hit.nearTimes.y >= 0 && hit.nearTimes.y < 1){ + // Any tilemap objects that made it here are collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + node._velocity.y = node._velocity.y * tneary; + node.isColliding = true; + } + } + } + } + + /*---------- INFORMATION/TRIGGER PHASE ----------*/ + // Check if we ended up on the ground, ceiling or wall + // Also check for triggers + for(let overlap of overlaps){ + // Check for a trigger. If we care about the trigger, react + if(overlap.other.isTrigger && (overlap.other.triggerMask & node.group)){ + // Get the bit that this group is represented by + let index = Math.floor(Math.log2(node.group)); + + // Extract the triggerEnter event name + this.emitter.fireEvent(overlap.other.triggerEnters[index], { + node: (node).id, + other: (overlap.other).id + }); + } + + // Ignore collision sides for nodes we don't interact with + if( groupIndex !== -1 && overlap.other.group !== -1 && ((this.collisionMasks[groupIndex] & overlap.other.group) === 0)) continue; + + // Only check for direction if the overlap was collidable + if(overlap.type === "Tilemap" || overlap.other.isCollidable){ + let collisionSide = overlap.collider.touchesAABBWithoutCorners(node.collisionShape.getBoundingRect()); + if(collisionSide !== null){ + // If we touch, not including corner cases, check the collision normal + if(overlap.hit !== null){ + // If we hit a tilemap, keep track of it + if(overlap.type == "Tilemap"){ + node.collidedWithTilemap = true; + } + + if(collisionSide.y === -1){ + // Node is on top of overlap, so onGround + node.onGround = true; + } else if(collisionSide.y === 1){ + // Node is on bottom of overlap, so onCeiling + node.onCeiling = true; + } else { + // Node wasn't touching on y, so it is touching on x + node.onWall = true; + } + } + } + } + } + + // Resolve the collision with the node, and move it + node.finishMove(); + } + } + + /** + * Handles a collision between this node and an orthogonal tilemap + * @param node The node + * @param tilemap The tilemap the node may be colliding with + * @param overlaps The list of overlaps + */ + protected collideWithOrthogonalTilemap(node: Physical, tilemap: OrthogonalTilemap, overlaps: Array): void { + // Get the min and max x and y coordinates of the moving node + let min = new Vec2(node.sweptRect.left, node.sweptRect.top); + let max = new Vec2(node.sweptRect.right, node.sweptRect.bottom); + + // Convert the min/max x/y to the min and max row/col in the tilemap array + let minIndex = tilemap.getColRowAt(min); + let maxIndex = tilemap.getColRowAt(max); + + let tileSize = tilemap.getTileSize(); + + // Loop over all possible tiles (which isn't many in the scope of the velocity per frame) + for(let col = minIndex.x; col <= maxIndex.x; col++){ + for(let row = minIndex.y; row <= maxIndex.y; row++){ + if(tilemap.isTileCollidable(col, row)){ + // Get the position of this tile + let tilePos = new Vec2(col * tileSize.x + tileSize.x/2, row * tileSize.y + tileSize.y/2); + + // Create a new collider for this tile + let collider = new AABB(tilePos, tileSize.scaled(1/2)); + + // Calculate collision area between the node and the tile + let area = node.sweptRect.overlapArea(collider); + if(area > 0){ + // We had a collision + overlaps.push(new AreaCollision(area, collider, tilemap, "Tilemap", new Vec2(col, row))); + } + } + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Physics/PhysicsManager.ts b/hw3/src/Wolfie2D/Physics/PhysicsManager.ts new file mode 100644 index 0000000..b35bccd --- /dev/null +++ b/hw3/src/Wolfie2D/Physics/PhysicsManager.ts @@ -0,0 +1,112 @@ +import Updateable from "../DataTypes/Interfaces/Updateable"; +import Tilemap from "../Nodes/Tilemap"; +import Receiver from "../Events/Receiver"; +import Emitter from "../Events/Emitter"; +import Map from "../DataTypes/Map"; +import Physical from "../DataTypes/Interfaces/Physical"; + +/** + * An abstract physics manager. + * This class exposes functions for subclasses to implement that should allow for a working physics system to be created. + */ +export default abstract class PhysicsManager implements Updateable { + /** The event receiver for the physics system */ + protected receiver: Receiver; + /** The event emitter for the physics system */ + protected emitter: Emitter; + + /** Maps layer names to numbers */ + protected groupMap: Map; + + /** Maps layer numbers to names */ + protected groupNames: Array; + + /** The default group name */ + protected static readonly DEFAULT_GROUP = "Default"; + + constructor(){ + this.receiver = new Receiver(); + this.emitter = new Emitter(); + + // The creation and implementation of layers is deferred to the subclass + this.groupMap = new Map(); + this.groupNames = new Array(); + } + + destroy(): void { + this.receiver.destroy(); + } + + /** + * Registers a gamenode with this physics manager + * @param object The object to register + */ + abstract registerObject(object: Physical): void; + + + /** + * Removes references to this object from the physics managerr + * @param object The object to deregister + */ + abstract deregisterObject(object: Physical): void; + + /** + * Registers a tilemap with this physics manager + * @param tilemap The tilemap to register + */ + abstract registerTilemap(tilemap: Tilemap): void; + + /** + * Removes references to this tilemap from the physics managerr + * @param tilemap The object to deregister + */ + abstract deregisterTilemap(tilemap: Tilemap): void; + + abstract update(deltaT: number): void; + + /** + * Sets the physics layer of the GameNode + * @param node The GameNode + * @param group The group that the GameNode should be on + */ + setGroup(node: Physical, group: string): void { + node.group = this.groupMap.get(group); + } + + /** + * Retrieves the layer number associated with the provided name + * @param layer The name of the layer + * @returns The layer number, or 0 if there is not a layer with that name registered + */ + getGroupNumber(group: string): number { + if(this.groupMap.has(group)){ + return this.groupMap.get(group); + } else{ + return 0; + } + } + + /** + * Gets all group names associated with the number provided + * @param groups A mask of groups + * @returns All groups contained in the mask + */ + getGroupNames(groups: number): Array { + if(groups === -1){ + return [PhysicsManager.DEFAULT_GROUP]; + } else { + let g = 1; + let names = []; + + for(let i = 0; i < 32; i++){ + if(g & groups){ + // This group is in the groups number + names.push(this.groupNames[i]); + } + + // Shift the bit over + g = g << 1; + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Playback/Recorder.ts b/hw3/src/Wolfie2D/Playback/Recorder.ts new file mode 100644 index 0000000..676cb09 --- /dev/null +++ b/hw3/src/Wolfie2D/Playback/Recorder.ts @@ -0,0 +1,93 @@ +import Queue from "../DataTypes/Queue"; +import Receiver from "../Events/Receiver"; +import GameEvent from "../Events/GameEvent"; +import EventQueue from "../Events/EventQueue"; +import { GameEventType } from "../Events/GameEventType"; + +// @ignorePage + +export default class Recorder { + private receiver: Receiver; + private log: Queue; + private recording: boolean; + private eventQueue: EventQueue; + private frame: number; + private playing: boolean; + + constructor(){ + this.receiver = new Receiver(); + this.log = new Queue(1000); + this.recording = false; + this.playing = false; + this.frame = 0; + + this.eventQueue = EventQueue.getInstance(); + this.eventQueue.subscribe(this.receiver, "all"); + } + + update(deltaT: number): void { + if(this.recording){ + this.frame += 1; + } + + if(this.playing){ + // If playing, ignore events, just feed the record to the event queue + this.receiver.ignoreEvents(); + + /* + While there is a next item, and while it should occur in this frame, + send the event. i.e., while current_frame * current_delta_t is greater + than recorded_frame * recorded_delta_t + */ + while(this.log.hasItems() + && this.log.peekNext().frame * this.log.peekNext().delta < this.frame * deltaT){ + let event = this.log.dequeue().event; + console.log(event); + this.eventQueue.addEvent(event); + } + + if(!this.log.hasItems()){ + this.playing = false; + } + + this.frame += 1; + } else { + // If not playing, handle events + while(this.receiver.hasNextEvent()){ + let event = this.receiver.getNextEvent(); + + if(event.type === GameEventType.STOP_RECORDING){ + this.recording = false; + } + + if(this.recording){ + this.log.enqueue(new LogItem(this.frame, deltaT, event)); + } + + if(event.type === GameEventType.START_RECORDING){ + this.log.clear(); + this.recording = true; + this.frame = 0 + } + + if(event.type === GameEventType.PLAY_RECORDING){ + this.frame = 0; + this.recording = false; + this.playing = true; + } + } + } + } +} + +class LogItem { + frame: number; + delta: number; + event: GameEvent; + + constructor(frame: number, deltaT: number, event: GameEvent){ + this.frame = frame; + this.delta = deltaT; + this.event = event; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Registry/Registries/FontRegistry.ts b/hw3/src/Wolfie2D/Registry/Registries/FontRegistry.ts new file mode 100644 index 0000000..e69de29 diff --git a/hw3/src/Wolfie2D/Registry/Registries/Registry.ts b/hw3/src/Wolfie2D/Registry/Registries/Registry.ts new file mode 100644 index 0000000..f67d135 --- /dev/null +++ b/hw3/src/Wolfie2D/Registry/Registries/Registry.ts @@ -0,0 +1,21 @@ +import Map from "../../DataTypes/Map"; + +export default abstract class Registry extends Map{ + + /** Preloads registry data */ + public abstract preload(): void; + + /** + * Registers an item and preloads any necessary files + * @param key The key to register this item with + * @param args Any additional arguments needed for registration + */ + public abstract registerAndPreloadItem(key: string, ...args: any): void; + + /** + * Registers an item and preloads any necessary files + * @param key The key to register this item with + * @param args Any aditional arguments needed for registration + */ + public abstract registerItem(key: string, ...args: any): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts b/hw3/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts new file mode 100644 index 0000000..7e566a3 --- /dev/null +++ b/hw3/src/Wolfie2D/Registry/Registries/ShaderRegistry.ts @@ -0,0 +1,99 @@ +import Map from "../../DataTypes/Map"; +import ShaderType from "../../Rendering/WebGLRendering/ShaderType"; +import LabelShaderType from "../../Rendering/WebGLRendering/ShaderTypes/LabelShaderType"; +import PointShaderType from "../../Rendering/WebGLRendering/ShaderTypes/PointShaderType"; +import RectShaderType from "../../Rendering/WebGLRendering/ShaderTypes/RectShaderType"; +import SpriteShaderType from "../../Rendering/WebGLRendering/ShaderTypes/SpriteShaderType"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Registry from "./Registry"; + +/** + * A registry that handles shaders + */ +export default class ShaderRegistry extends Registry { + + // Shader names + public static POINT_SHADER = "point"; + public static RECT_SHADER = "rect"; + public static SPRITE_SHADER = "sprite"; + public static LABEL_SHADER = "label"; + + private registryItems: Array = new Array(); + + /** + * Preloads all built-in shaders + */ + public preload(){ + // Get the resourceManager and queue all built-in shaders for preloading + const rm = ResourceManager.getInstance(); + + // Queue a load for the point shader + this.registerAndPreloadItem(ShaderRegistry.POINT_SHADER, PointShaderType, "builtin/shaders/point.vshader", "builtin/shaders/point.fshader"); + + // Queue a load for the rect shader + this.registerAndPreloadItem(ShaderRegistry.RECT_SHADER, RectShaderType, "builtin/shaders/rect.vshader", "builtin/shaders/rect.fshader"); + + // Queue a load for the sprite shader + this.registerAndPreloadItem(ShaderRegistry.SPRITE_SHADER, SpriteShaderType, "builtin/shaders/sprite.vshader", "builtin/shaders/sprite.fshader"); + + // Queue a load for the label shader + this.registerAndPreloadItem(ShaderRegistry.LABEL_SHADER, LabelShaderType, "builtin/shaders/label.vshader", "builtin/shaders/label.fshader"); + + // Queue a load for any preloaded items + for(let item of this.registryItems){ + const shader = new item.constr(item.key); + shader.initBufferObject(); + this.add(item.key, shader); + + // Load if desired + if(item.preload !== undefined){ + rm.shader(item.key, item.preload.vshaderLocation, item.preload.fshaderLocation); + } + } + } + + /** + * Registers a shader in the registry and loads it before the game begins + * @param key The key you wish to assign to the shader + * @param constr The constructor of the ShaderType + * @param vshaderLocation The location of the vertex shader + * @param fshaderLocation the location of the fragment shader + */ + public registerAndPreloadItem(key: string, constr: new (programKey: string) => ShaderType, vshaderLocation: string, fshaderLocation: string): void { + let shaderPreload = new ShaderPreload(); + shaderPreload.vshaderLocation = vshaderLocation; + shaderPreload.fshaderLocation = fshaderLocation; + + let registryItem = new ShaderRegistryItem(); + registryItem.key = key; + registryItem.constr = constr; + registryItem.preload = shaderPreload; + + this.registryItems.push(registryItem); + } + + /** + * Registers a shader in the registry. NOTE: If you use this, you MUST load the shader before use. + * If you wish to preload the shader, use registerAndPreloadItem() + * @param key The key you wish to assign to the shader + * @param constr The constructor of the ShaderType + */ + public registerItem(key: string, constr: new (programKey: string) => ShaderType): void { + let registryItem = new ShaderRegistryItem(); + registryItem.key = key; + registryItem.constr = constr; + + this.registryItems.push(registryItem); + } +} + +class ShaderRegistryItem { + key: string; + constr: new (programKey: string) => ShaderType; + preload: ShaderPreload; +} + +class ShaderPreload { + vshaderLocation: string; + fshaderLocation: string; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Registry/RegistryManager.ts b/hw3/src/Wolfie2D/Registry/RegistryManager.ts new file mode 100644 index 0000000..d3cd802 --- /dev/null +++ b/hw3/src/Wolfie2D/Registry/RegistryManager.ts @@ -0,0 +1,31 @@ +import Map from "../DataTypes/Map"; +import Registry from "./Registries/Registry"; +import ShaderRegistry from "./Registries/ShaderRegistry"; + +/** + * The Registry is the system's way of converting classes and types into string + * representations for use elsewhere in the application. + * It allows classes to be accessed without explicitly using constructors in code, + * and for resources to be loaded at Game creation time. + */ +export default class RegistryManager { + + public static shaders = new ShaderRegistry(); + + /** Additional custom registries to add to the registry manager */ + protected static registries: Map> = new Map(); + + static preload(){ + this.shaders.preload(); + + this.registries.forEach((key: string) => this.registries.get(key).preload()); + } + + static addCustomRegistry(name: string, registry: Registry){ + this.registries.add(name, registry); + } + + static getRegistry(key: string){ + return this.registries.get(key); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/Animations/AnimationManager.ts b/hw3/src/Wolfie2D/Rendering/Animations/AnimationManager.ts new file mode 100644 index 0000000..a976ef3 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/Animations/AnimationManager.ts @@ -0,0 +1,228 @@ +import Map from "../../DataTypes/Map"; +import Emitter from "../../Events/Emitter"; +import CanvasNode from "../../Nodes/CanvasNode"; +import { AnimationData, AnimationState } from "./AnimationTypes"; + +/** + * An animation manager class for an animated CanvasNode. + * This class keeps track of the possible animations, as well as the current animation state, + * and abstracts all interactions with playing, pausing, and stopping animations as well as + * creating new animations from the CanvasNode. + */ +export default class AnimationManager { + /** The owner of this animation manager */ + protected owner: CanvasNode; + + /** The current animation state of this sprite */ + protected animationState: AnimationState; + + /** The name of the current animation of this sprite */ + protected currentAnimation: string; + + /** The current frame of this animation */ + protected currentFrame: number; + + /** The progress of the current animation through the current frame */ + protected frameProgress: number; + + /** Whether the current animation is looping or not */ + protected loop: boolean; + + /** The map of animations */ + protected animations: Map; + + /** The name of the event (if any) to send when the current animation stops playing. */ + protected onEndEvent: string; + + /** The event emitter for this animation manager */ + protected emitter: Emitter; + + /** A queued animation */ + protected pendingAnimation: string; + + /** The loop status of a pending animation */ + protected pendingLoop: boolean; + + /** The onEnd event of a pending animation */ + protected pendingOnEnd: string; + + /** + * Creates a new AnimationManager + * @param owner The owner of the AnimationManager + */ + constructor(owner: CanvasNode){ + this.owner = owner; + this.animationState = AnimationState.STOPPED; + this.currentAnimation = ""; + this.currentFrame = 0; + this.frameProgress = 0; + this.loop = false; + this.animations = new Map(); + this.onEndEvent = null; + this.emitter = new Emitter(); + } + + /** + * Add an animation to this sprite + * @param key The unique key of the animation + * @param animation The animation data + */ + add(key: string, animation: AnimationData): void { + this.animations.add(key, animation); + } + + /** + * Gets the index specified by the current animation and current frame + * @returns The index in the current animation + */ + getIndex(): number { + if(this.animations.has(this.currentAnimation)){ + return this.animations.get(this.currentAnimation).frames[this.currentFrame].index; + } else { + // No current animation, warn the user + console.warn(`Animation index was requested, but the current animation: ${this.currentAnimation} was invalid`); + return 0; + } + } + + /** + * Determines whether the specified animation is currently playing + * @param key The key of the animation to check + * @returns true if the specified animation is playing, false otherwise + */ + isPlaying(key: string): boolean { + return this.currentAnimation === key && this.animationState === AnimationState.PLAYING; + } + + /** + * Retrieves the current animation index and advances the animation frame + * @returns The index of the animation frame + */ + getIndexAndAdvanceAnimation(): number { + // If we aren't playing, we won't be advancing the animation + if(!(this.animationState === AnimationState.PLAYING)){ + return this.getIndex(); + } + + if(this.animations.has(this.currentAnimation)){ + let currentAnimation = this.animations.get(this.currentAnimation); + let index = currentAnimation.frames[this.currentFrame].index; + + // Advance the animation + this.frameProgress += 1; + if(this.frameProgress >= currentAnimation.frames[this.currentFrame].duration){ + // We have been on this frame for its whole duration, go to the next one + this.frameProgress = 0; + this.currentFrame += 1; + + if(this.currentFrame >= currentAnimation.frames.length){ + // We have reached the end of this animation + if(this.loop){ + this.currentFrame = 0; + this.frameProgress = 0; + } else { + this.endCurrentAnimation(); + } + } + } + + // Return the current index + return index; + } else { + // No current animation, can't advance. Warn the user + console.warn(`Animation index and advance was requested, but the current animation (${this.currentAnimation}) in node with id: ${this.owner.id} was invalid`); + return 0; + } + } + + /** Ends the current animation and fires any necessary events, as well as starting any new animations */ + protected endCurrentAnimation(): void { + this.currentFrame = 0; + this.animationState = AnimationState.STOPPED; + + if(this.onEndEvent !== null){ + this.emitter.fireEvent(this.onEndEvent, {owner: this.owner.id, animation: this.currentAnimation}); + } + + // If there is a pending animation, play it + if(this.pendingAnimation !== null){ + this.play(this.pendingAnimation, this.pendingLoop, this.pendingOnEnd); + } + } + + /** + * Plays the specified animation. Does not restart it if it is already playing + * @param animation The name of the animation to play + * @param loop Whether or not to loop the animation. False by default + * @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false. + */ + playIfNotAlready(animation: string, loop?: boolean, onEnd?: string): void { + if(this.currentAnimation !== animation){ + this.play(animation, loop, onEnd); + } + } + + /** + * Plays the specified animation + * @param animation The name of the animation to play + * @param loop Whether or not to loop the animation. False by default + * @param onEnd The name of an event to send when this animation naturally stops playing. This only matters if loop is false. + */ + play(animation: string, loop?: boolean, onEnd?: string): void { + this.currentAnimation = animation; + this.currentFrame = 0; + this.frameProgress = 0; + this.animationState = AnimationState.PLAYING; + + // If loop arg was provided, use that + if(loop !== undefined){ + this.loop = loop; + } else { + // Otherwise, use what the json file specified + this.loop = this.animations.get(animation).repeat; + } + + if(onEnd !== undefined){ + this.onEndEvent = onEnd; + } else { + this.onEndEvent = null; + } + + // Reset pending animation + this.pendingAnimation = null; + } + + /** + * Queues a single animation to be played after the current one. Does NOT stack. + * Queueing additional animations past 1 will just replace the queued animation + * @param animation The animation to queue + * @param loop Whether or not the loop the queued animation + * @param onEnd The event to fire when the queued animation ends + */ + queue(animation: string, loop: boolean = false, onEnd?: string): void { + this.pendingAnimation = animation; + this.pendingLoop = loop; + if(onEnd !== undefined){ + this.pendingOnEnd = onEnd; + } else { + this.pendingOnEnd = null; + } + } + + /** Pauses the current animation */ + pause(): void { + this.animationState = AnimationState.PAUSED; + } + + /** Resumes the current animation if possible */ + resume(): void { + if(this.animationState === AnimationState.PAUSED){ + this.animationState = AnimationState.PLAYING; + } + } + + /** Stops the current animation. The animation cannot be resumed after this. */ + stop(): void { + this.animationState = AnimationState.STOPPED; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/Animations/AnimationTypes.ts b/hw3/src/Wolfie2D/Rendering/Animations/AnimationTypes.ts new file mode 100644 index 0000000..5f172de --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/Animations/AnimationTypes.ts @@ -0,0 +1,65 @@ +import { TweenableProperties } from "../../Nodes/GameNode"; +import { EaseFunctionType } from "../../Utils/EaseFunctions"; + +// @ignorePage + +export enum AnimationState { + STOPPED = 0, + PAUSED = 1, + PLAYING = 2, +} + +export class AnimationData { + name: string; + frames: Array<{index: number, duration: number}>; + repeat: boolean = false; +} + +export class TweenEffect { + /** The property to tween */ + property: TweenableProperties; + + /** Whether or not the Tween should reset the property to its original value after playing */ + resetOnComplete: boolean; + + /** The starting value for the tween */ + start: any; + + /** The ending value for the tween */ + end: any; + + /** The ease function to use */ + ease: EaseFunctionType; + + /** DO NOT MODIFY - The original value of the property - set automatically */ + initialValue: number; +} + +export class TweenData { + // Members for initialization by the user + /** The amount of time in ms to wait before executing the tween */ + startDelay: number; + /** The duration of time over which the value with change from start to end */ + duration: number; + /** An array of the effects on the properties of the object */ + effects: Array; + /** Whether or not this tween should reverse from end to start for each property when it finishes */ + reverseOnComplete: boolean; + /** Whether or not this tween should loop when it completes */ + loop: boolean; + /** The name of the event to send (if any) when the tween finishes playing */ + onEnd: string + + // Members for management by the tween manager + /** The progress of this tween through its effects */ + progress: number; + + /** The amount of time in ms that has passed from when this tween started running */ + elapsedTime: number; + + /** The state of this tween */ + animationState: AnimationState; + + /** Whether or not this tween is currently reversing */ + reversing: boolean; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/Animations/TweenController.ts b/hw3/src/Wolfie2D/Rendering/Animations/TweenController.ts new file mode 100644 index 0000000..082208d --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/Animations/TweenController.ts @@ -0,0 +1,213 @@ +import Map from "../../DataTypes/Map"; +import GameNode from "../../Nodes/GameNode"; +import { AnimationState, TweenData } from "./AnimationTypes"; +import EaseFunctions from "../../Utils/EaseFunctions"; +import MathUtils from "../../Utils/MathUtils"; +import TweenManager from "./TweenManager"; +import Emitter from "../../Events/Emitter"; + +/** + * A manager for the tweens of a GameNode. + * Tweens are short animations played by interpolating between two properties using an easing function. + * For a good visual representation of easing functions, check out @link(https://easings.net/)(https://easings.net/). + * Multiple tween can be played at the same time, as long as they don't change the same property. + * This allows for some interesting polishes or animations that may be very difficult to do with sprite work alone + * - especially pixel art (such as rotations or scaling). + */ +export default class TweenController { + /** The GameNode this TweenController acts upon */ + protected owner: GameNode; + /** The list of created tweens */ + protected tweens: Map; + /** An event emitter */ + protected emitter: Emitter; + + /** + * Creates a new TweenController + * @param owner The owner of the TweenController + */ + constructor(owner: GameNode){ + this.owner = owner; + this.tweens = new Map(); + this.emitter = new Emitter(); + + // Give ourselves to the TweenManager + TweenManager.getInstance().registerTweenController(this); + } + + /** + * Destroys this TweenController + */ + destroy(){ + // Only the gamenode and the tween manager should have a reference to this + delete this.owner.tweens; + TweenManager.getInstance().deregisterTweenController(this); + } + + /** + * Add a tween to this game node + * @param key The name of the tween + * @param tween The data of the tween + */ + add(key: string, tween: Record | TweenData): void { + let typedTween = tween; + + // Initialize members that we need (and the user didn't provide) + typedTween.progress = 0; + typedTween.elapsedTime = 0; + typedTween.animationState = AnimationState.STOPPED; + + this.tweens.add(key, typedTween); + } + + /** + * Play a tween with a certain name + * @param key The name of the tween to play + * @param loop Whether or not the tween should loop + */ + play(key: string, loop?: boolean): void { + if(this.tweens.has(key)){ + let tween = this.tweens.get(key); + + // Set loop if needed + if(loop !== undefined){ + tween.loop = loop; + } + + // Set the initial values + for(let effect of tween.effects){ + if(effect.resetOnComplete){ + effect.initialValue = this.owner[effect.property]; + } + } + + // Start the tween running + tween.animationState = AnimationState.PLAYING; + tween.elapsedTime = 0; + tween.progress = 0; + tween.reversing = false; + } else { + console.warn(`Tried to play tween "${key}" on node with id ${this.owner.id}, but no such tween exists`); + } + } + + /** + * Pauses a playing tween. Does not affect tweens that are stopped. + * @param key The name of the tween to pause. + */ + pause(key: string): void { + if(this.tweens.has(key)){ + this.tweens.get(key).animationState = AnimationState.PAUSED; + } + } + + /** + * Resumes a paused tween. + * @param key The name of the tween to resume + */ + resume(key: string): void { + if(this.tweens.has(key)){ + let tween = this.tweens.get(key); + if(tween.animationState === AnimationState.PAUSED) + tween.animationState = AnimationState.PLAYING; + } + } + + /** + * Stops a currently playing tween + * @param key The key of the tween + */ + stop(key: string): void { + if(this.tweens.has(key)){ + let tween = this.tweens.get(key); + tween.animationState = AnimationState.STOPPED; + + // Return to the initial values + for(let effect of tween.effects){ + if(effect.resetOnComplete){ + this.owner[effect.property] = effect.initialValue; + } + } + } + } + + /** + * The natural stop of a currently playing tween + * @param key The key of the tween + */ + protected end(key: string): void { + this.stop(key); + if(this.tweens.has(key)){ + // Get the tween + let tween = this.tweens.get(key); + + // If it has an onEnd, send an event + if(tween.onEnd){ + this.emitter.fireEvent(tween.onEnd, {key: key, node: this.owner.id}); + } + } + } + + /** + * Stops all currently playing tweens + */ + stopAll(): void { + this.tweens.forEach(key => this.stop(key)); + } + + update(deltaT: number): void { + this.tweens.forEach(key => { + let tween = this.tweens.get(key); + if(tween.animationState === AnimationState.PLAYING){ + // Update the progress of the tween + tween.elapsedTime += deltaT*1000; + + // If we're past the startDelay, do the tween + if(tween.elapsedTime >= tween.startDelay){ + if(!tween.reversing && tween.elapsedTime >= tween.startDelay + tween.duration){ + // If we're over time, stop the tween, loop, or reverse + if(tween.reverseOnComplete){ + // If we're over time and can reverse, do so + tween.reversing = true; + } else if(tween.loop){ + // If we can't reverse and can loop, do so + tween.elapsedTime -= tween.duration; + } else { + // We aren't looping and can't reverse, so stop + this.end(key); + } + } + + // Check for the end of reversing + if(tween.reversing && tween.elapsedTime >= tween.startDelay + 2*tween.duration){ + if(tween.loop){ + tween.reversing = false; + tween.elapsedTime -= 2*tween.duration; + } else { + this.end(key); + } + } + + // Update the progress, make sure it is between 0 and 1. Errors from this should never be large + if(tween.reversing){ + tween.progress = MathUtils.clamp01((2*tween.duration - (tween.elapsedTime- tween.startDelay))/tween.duration); + } else { + tween.progress = MathUtils.clamp01((tween.elapsedTime - tween.startDelay)/tween.duration); + } + + for(let effect of tween.effects){ + + // Get the value from the ease function that corresponds to our progress + let ease = EaseFunctions[effect.ease](tween.progress); + + // Use the value to lerp the property + let value = MathUtils.lerp(effect.start, effect.end, ease); + + // Assign the value of the property + this.owner[effect.property] = value; + } + } + } + }); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/Animations/TweenManager.ts b/hw3/src/Wolfie2D/Rendering/Animations/TweenManager.ts new file mode 100644 index 0000000..ad33c3e --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/Animations/TweenManager.ts @@ -0,0 +1,40 @@ +import Updateable from "../../DataTypes/Interfaces/Updateable"; +import TweenController from "./TweenController"; + +export default class TweenManager implements Updateable { + + private static instance: TweenManager = null; + + protected tweenControllers: Array; + + private constructor(){ + this.tweenControllers = new Array(); + } + + static getInstance(): TweenManager { + if(TweenManager.instance === null){ + TweenManager.instance = new TweenManager(); + } + + return TweenManager.instance; + } + + registerTweenController(controller: TweenController){ + this.tweenControllers.push(controller); + } + + deregisterTweenController(controller: TweenController){ + let index = this.tweenControllers.indexOf(controller); + this.tweenControllers.splice(index, 1); + } + + clearTweenControllers(){ + this.tweenControllers = new Array(); + } + + update(deltaT: number): void { + for(let tweenController of this.tweenControllers){ + tweenController.update(deltaT); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/CanvasRenderer.ts b/hw3/src/Wolfie2D/Rendering/CanvasRenderer.ts new file mode 100644 index 0000000..152c435 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/CanvasRenderer.ts @@ -0,0 +1,255 @@ +import Map from "../DataTypes/Map"; +import CanvasNode from "../Nodes/CanvasNode"; +import Graphic from "../Nodes/Graphic"; +import Point from "../Nodes/Graphics/Point"; +import Rect from "../Nodes/Graphics/Rect"; +import Sprite from "../Nodes/Sprites/Sprite"; +import Tilemap from "../Nodes/Tilemap"; +import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap"; +import UIElement from "../Nodes/UIElement"; +import UILayer from "../Scene/Layers/UILayer"; +import Scene from "../Scene/Scene"; +import GraphicRenderer from "./CanvasRendering/GraphicRenderer"; +import RenderingManager from "./RenderingManager" +import TilemapRenderer from "./CanvasRendering/TilemapRenderer"; +import UIElementRenderer from "./CanvasRendering/UIElementRenderer"; +import Label from "../Nodes/UIElements/Label"; +import Button from "../Nodes/UIElements/Button"; +import Slider from "../Nodes/UIElements/Slider"; +import TextInput from "../Nodes/UIElements/TextInput"; +import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; +import Vec2 from "../DataTypes/Vec2"; +import Color from "../Utils/Color"; +import Line from "../Nodes/Graphics/Line"; +import Debug from "../Debug/Debug"; + +/** + * An implementation of the RenderingManager class using CanvasRenderingContext2D. + */ +export default class CanvasRenderer extends RenderingManager { + protected ctx: CanvasRenderingContext2D; + protected graphicRenderer: GraphicRenderer; + protected tilemapRenderer: TilemapRenderer; + protected uiElementRenderer: UIElementRenderer; + + protected origin: Vec2; + protected zoom: number; + + protected worldSize: Vec2; + + constructor(){ + super(); + } + + // @override + setScene(scene: Scene){ + this.scene = scene; + this.graphicRenderer.setScene(scene); + this.tilemapRenderer.setScene(scene); + this.uiElementRenderer.setScene(scene); + } + + // @override + initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { + canvas.width = width; + canvas.height = height; + + this.worldSize = new Vec2(width, height); + + this.ctx = canvas.getContext("2d"); + + this.graphicRenderer = new GraphicRenderer(this.ctx); + this.tilemapRenderer = new TilemapRenderer(this.ctx); + this.uiElementRenderer = new UIElementRenderer(this.ctx) + + // For crisp pixel art + this.ctx.imageSmoothingEnabled = false; + + return this.ctx; + } + + // @override + render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map): void { + // Sort by depth, then by visible set by y-value + visibleSet.sort((a, b) => { + if(a.getLayer().getDepth() === b.getLayer().getDepth()){ + return (a.boundary.bottom) - (b.boundary.bottom); + } else { + return a.getLayer().getDepth() - b.getLayer().getDepth(); + } + }); + + let tilemapIndex = 0; + let tilemapLength = tilemaps.length; + + let visibleSetIndex = 0; + let visibleSetLength = visibleSet.length; + + while(tilemapIndex < tilemapLength || visibleSetIndex < visibleSetLength){ + // Check conditions where we've already reached the edge of one list + if(tilemapIndex >= tilemapLength){ + // Only render the remaining visible set + let node = visibleSet[visibleSetIndex++]; + if(node.visible){ + this.renderNode(node); + } + continue; + } + + if(visibleSetIndex >= visibleSetLength){ + // Only render tilemaps + this.renderTilemap(tilemaps[tilemapIndex++]); + continue; + } + + // Render whichever is further down + if(tilemaps[tilemapIndex].getLayer().getDepth() <= visibleSet[visibleSetIndex].getLayer().getDepth()){ + this.renderTilemap(tilemaps[tilemapIndex++]); + } else { + let node = visibleSet[visibleSetIndex++]; + if(node.visible){ + this.renderNode(node); + } + } + } + + // Render the uiLayers on top of everything else + let sortedUILayers = new Array(); + + uiLayers.forEach(key => sortedUILayers.push(uiLayers.get(key))); + + sortedUILayers = sortedUILayers.sort((ui1, ui2) => ui1.getDepth() - ui2.getDepth()); + + sortedUILayers.forEach(uiLayer => { + if(!uiLayer.isHidden()) + uiLayer.getItems().forEach(node => { + if((node).visible){ + this.renderNode(node) + } + }) + }); + } + + /** + * Renders a specified CanvasNode + * @param node The CanvasNode to render + */ + protected renderNode(node: CanvasNode): void { + // Calculate the origin of the viewport according to this sprite + this.origin = this.scene.getViewTranslation(node); + + // Get the zoom level of the scene + this.zoom = this.scene.getViewScale(); + + // Move the canvas to the position of the node and rotate + let xScale = 1; + let yScale = 1; + + if(node instanceof Sprite){ + xScale = node.invertX ? -1 : 1; + yScale = node.invertY ? -1 : 1; + } + + this.ctx.setTransform(xScale, 0, 0, yScale, (node.position.x - this.origin.x)*this.zoom, (node.position.y - this.origin.y)*this.zoom); + this.ctx.rotate(-node.rotation); + let globalAlpha = this.ctx.globalAlpha; + if(node instanceof Rect){ + Debug.log("node" + node.id, "Node" + node.id + " Alpha: " + node.alpha); + } + this.ctx.globalAlpha = node.alpha; + + if(node instanceof AnimatedSprite){ + this.renderAnimatedSprite(node); + } else if(node instanceof Sprite){ + this.renderSprite(node); + } else if(node instanceof Graphic){ + this.renderGraphic(node); + } else if(node instanceof UIElement){ + this.renderUIElement(node); + } + + this.ctx.globalAlpha = globalAlpha; + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + } + + // @override + protected renderSprite(sprite: Sprite): void { + // Get the image from the resource manager + let image = this.resourceManager.getImage(sprite.imageId); + + /* + Coordinates in the space of the image: + image crop start -> x, y + image crop size -> w, h + Coordinates in the space of the world + image draw start -> x, y + image draw size -> w, h + */ + this.ctx.drawImage(image, + sprite.imageOffset.x, sprite.imageOffset.y, + sprite.size.x, sprite.size.y, + (-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom, + sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom); + } + + // @override + protected renderAnimatedSprite(sprite: AnimatedSprite): void { + // Get the image from the resource manager + let image = this.resourceManager.getImage(sprite.imageId); + + let animationIndex = sprite.animation.getIndexAndAdvanceAnimation(); + + let animationOffset = sprite.getAnimationOffset(animationIndex); + + /* + Coordinates in the space of the image: + image crop start -> x, y + image crop size -> w, h + Coordinates in the space of the world (given we moved) + image draw start -> -w/2, -h/2 + image draw size -> w, h + */ + this.ctx.drawImage(image, + sprite.imageOffset.x + animationOffset.x, sprite.imageOffset.y + animationOffset.y, + sprite.size.x, sprite.size.y, + (-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom, + sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom); + } + + // @override + protected renderGraphic(graphic: Graphic): void { + if(graphic instanceof Point){ + this.graphicRenderer.renderPoint(graphic, this.zoom); + } else if(graphic instanceof Line){ + this.graphicRenderer.renderLine(graphic, this.origin, this.zoom); + } else if(graphic instanceof Rect){ + this.graphicRenderer.renderRect(graphic, this.zoom); + } + } + + // @override + protected renderTilemap(tilemap: Tilemap): void { + if(tilemap instanceof OrthogonalTilemap){ + this.tilemapRenderer.renderOrthogonalTilemap(tilemap); + } + } + + // @override + protected renderUIElement(uiElement: UIElement): void { + if(uiElement instanceof Label){ + this.uiElementRenderer.renderLabel(uiElement); + } else if(uiElement instanceof Button){ + this.uiElementRenderer.renderButton(uiElement); + } else if(uiElement instanceof Slider){ + this.uiElementRenderer.renderSlider(uiElement); + } else if(uiElement instanceof TextInput){ + this.uiElementRenderer.renderTextInput(uiElement); + } + } + + clear(clearColor: Color): void { + this.ctx.clearRect(0, 0, this.worldSize.x, this.worldSize.y); + this.ctx.fillStyle = clearColor.toString(); + this.ctx.fillRect(0, 0, this.worldSize.x, this.worldSize.y); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/CanvasRendering/GraphicRenderer.ts b/hw3/src/Wolfie2D/Rendering/CanvasRendering/GraphicRenderer.ts new file mode 100644 index 0000000..230249d --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/CanvasRendering/GraphicRenderer.ts @@ -0,0 +1,72 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Line from "../../Nodes/Graphics/Line"; +import Point from "../../Nodes/Graphics/Point"; +import Rect from "../../Nodes/Graphics/Rect"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Scene from "../../Scene/Scene"; + +/** + * A utility class to help the @reference[CanvasRenderer] render @reference[Graphic]s + */ +export default class GraphicRenderer { + /** The resource manager of the game engine */ + protected resourceManager: ResourceManager; + /** The current scene */ + protected scene: Scene; + /** The rendering context */ + protected ctx: CanvasRenderingContext2D; + + constructor(ctx: CanvasRenderingContext2D){ + this.resourceManager = ResourceManager.getInstance(); + this.ctx = ctx; + } + + /** + * Sets the scene of this GraphicRenderer + * @param scene The current scene + */ + setScene(scene: Scene): void { + this.scene = scene; + } + + /** + * Renders a point + * @param point The point to render + * @param zoom The zoom level + */ + renderPoint(point: Point, zoom: number): void { + this.ctx.fillStyle = point.color.toStringRGBA(); + this.ctx.fillRect((-point.size.x/2)*zoom, (-point.size.y/2)*zoom, + point.size.x*zoom, point.size.y*zoom); + } + + renderLine(line: Line, origin: Vec2, zoom: number): void { + this.ctx.strokeStyle = line.color.toStringRGBA(); + this.ctx.lineWidth = line.thickness; + this.ctx.beginPath(); + this.ctx.moveTo(0, 0); + this.ctx.lineTo((line.end.x - line.start.x)*zoom, (line.end.y - line.start.y)*zoom); + this.ctx.closePath(); + this.ctx.stroke(); + } + + /** + * Renders a rect + * @param rect The rect to render + * @param zoom The zoom level + */ + renderRect(rect: Rect, zoom: number): void { + // Draw the interior of the rect + if(rect.color.a !== 0){ + this.ctx.fillStyle = rect.color.toStringRGB(); + this.ctx.fillRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom); + } + + // Draw the border of the rect if it isn't transparent + if(rect.borderColor.a !== 0){ + this.ctx.strokeStyle = rect.getBorderColor().toStringRGB(); + this.ctx.lineWidth = rect.getBorderWidth(); + this.ctx.strokeRect((-rect.size.x/2)*zoom, (-rect.size.y/2)*zoom, rect.size.x*zoom, rect.size.y*zoom); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/CanvasRendering/TilemapRenderer.ts b/hw3/src/Wolfie2D/Rendering/CanvasRendering/TilemapRenderer.ts new file mode 100644 index 0000000..c7d06e7 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/CanvasRendering/TilemapRenderer.ts @@ -0,0 +1,151 @@ +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Scene from "../../Scene/Scene"; +import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap"; +import Vec2 from "../../DataTypes/Vec2"; +import Tileset from "../../DataTypes/Tilesets/Tileset"; + +/** + * A utility class for the @reference[CanvasRenderer] to render @reference[Tilemap]s + */ +export default class TilemapRenderer { + protected resourceManager: ResourceManager; + protected scene: Scene; + protected ctx: CanvasRenderingContext2D; + + constructor(ctx: CanvasRenderingContext2D){ + this.resourceManager = ResourceManager.getInstance(); + this.ctx = ctx; + } + + /** + * Sets the scene of this TilemapRenderer + * @param scene The current scene + */ + setScene(scene: Scene): void { + this.scene = scene; + } + + /** + * Renders an orthogonal tilemap + * @param tilemap The tilemap to render + */ + renderOrthogonalTilemap(tilemap: OrthogonalTilemap): void { + let previousAlpha = this.ctx.globalAlpha; + this.ctx.globalAlpha = tilemap.getLayer().getAlpha(); + + let origin = this.scene.getViewTranslation(tilemap); + let size = this.scene.getViewport().getHalfSize(); + let zoom = this.scene.getViewScale(); + let bottomRight = origin.clone().add(size.scaled(2*zoom)); + + if(tilemap.visible){ + let minColRow = tilemap.getColRowAt(origin); + let maxColRow = tilemap.getColRowAt(bottomRight); + + for(let x = minColRow.x; x <= maxColRow.x; x++){ + for(let y = minColRow.y; y <= maxColRow.y; y++){ + // Get the tile at this position + let tile = tilemap.getTileAtRowCol(new Vec2(x, y)); + + // Extract the rot/flip parameters if there are any + const mask = (0xE << 28); + const rotFlip = ((mask & tile) >> 28) & 0xF; + tile = tile & ~mask; + + // Find the tileset that owns this tile index and render + for(let tileset of tilemap.getTilesets()){ + if(tileset.hasTile(tile)){ + this.renderTile(tileset, tile, x, y, origin, tilemap.scale, zoom, rotFlip); + } + } + } + } + } + + this.ctx.globalAlpha = previousAlpha; + } + + /** + * Renders a tile + * @param tileset The tileset this tile belongs to + * @param tileIndex The index of the tile + * @param tilemapRow The row of the tile in the tilemap + * @param tilemapCol The column of the tile in the tilemap + * @param origin The origin of the viewport + * @param scale The scale of the tilemap + * @param zoom The zoom level of the viewport + */ + protected renderTile(tileset: Tileset, tileIndex: number, tilemapRow: number, tilemapCol: number, origin: Vec2, scale: Vec2, zoom: number, rotFlip: number): void { + let image = this.resourceManager.getImage(tileset.getImageKey()); + + // Get the true index + let index = tileIndex - tileset.getStartIndex(); + + // Get the row and col of the tile in image space + let row = Math.floor(index / tileset.getNumCols()); + let col = index % tileset.getNumCols(); + let width = tileset.getTileSize().x; + let height = tileset.getTileSize().y; + + // Calculate the position to start a crop in the tileset image + let left = col * width; + let top = row * height; + + // Calculate the position in the world to render the tile + let x = Math.floor(tilemapRow * width * scale.x); + let y = Math.floor(tilemapCol * height * scale.y); + + let worldX = Math.floor((x - origin.x)*zoom); + let worldY = Math.floor((y - origin.y)*zoom); + let worldWidth = Math.ceil(width * scale.x * zoom); + let worldHeight = Math.ceil(height * scale.y * zoom); + + if(rotFlip !== 0){ + let scaleX = 1; + let scaleY = 1; + let shearX = 0; + let shearY = 0; + + // Flip on the x-axis + if(rotFlip & 8){ + scaleX = -1; + } + + // Flip on the y-axis + if(rotFlip & 4){ + scaleY = -1; + } + + // Flip over the line y=x + if(rotFlip & 2){ + shearX = scaleY; + shearY = scaleX; + scaleX = 0; + scaleY = 0; + } + + this.ctx.setTransform(scaleX, shearX, shearY, scaleY, worldX + worldWidth/2, worldY + worldHeight/2); + + // Render the tile + this.ctx.drawImage(image, + left, top, + width, height, + -worldWidth/2, -worldHeight/2, + worldWidth, worldHeight); + + if(rotFlip !== 0){ + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + } + } else { + // No rotations, don't do the calculations, just render the tile + // Render the tile + this.ctx.drawImage(image, + left, top, + width, height, + worldX, worldY, + worldWidth, worldHeight); + } + + + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts b/hw3/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts new file mode 100644 index 0000000..56c872e --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/CanvasRendering/UIElementRenderer.ts @@ -0,0 +1,126 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Button from "../../Nodes/UIElements/Button"; +import Label from "../../Nodes/UIElements/Label"; +import Slider from "../../Nodes/UIElements/Slider"; +import TextInput from "../../Nodes/UIElements/TextInput"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Scene from "../../Scene/Scene"; +import MathUtils from "../../Utils/MathUtils"; + +/** + * A utility class to help the @reference[CanvasRenderer] render @reference[UIElement]s + */ +export default class UIElementRenderer { + protected resourceManager: ResourceManager; + protected scene: Scene; + protected ctx: CanvasRenderingContext2D; + + constructor(ctx: CanvasRenderingContext2D){ + this.resourceManager = ResourceManager.getInstance(); + this.ctx = ctx; + } + + /** + * Sets the scene of this UIElementRenderer + * @param scene The current scene + */ + setScene(scene: Scene): void { + this.scene = scene; + } + + /** + * Renders a label + * @param label The label to render + */ + renderLabel(label: Label): void { + // If the size is unassigned (by the user or automatically) assign it + label.handleInitialSizing(this.ctx); + + // Grab the global alpha so we can adjust it for this render + let previousAlpha = this.ctx.globalAlpha; + + // Get the font and text position in label + this.ctx.font = label.getFontString(); + let offset = label.calculateTextOffset(this.ctx); + + // Stroke and fill a rounded rect and give it text + this.ctx.globalAlpha = label.backgroundColor.a; + this.ctx.fillStyle = label.calculateBackgroundColor().toStringRGBA(); + this.ctx.fillRoundedRect(-label.size.x/2, -label.size.y/2, + label.size.x, label.size.y, label.borderRadius); + + this.ctx.strokeStyle = label.calculateBorderColor().toStringRGBA(); + this.ctx.globalAlpha = label.borderColor.a; + this.ctx.lineWidth = label.borderWidth; + this.ctx.strokeRoundedRect(-label.size.x/2, -label.size.y/2, + label.size.x, label.size.y, label.borderRadius); + + this.ctx.fillStyle = label.calculateTextColor(); + this.ctx.globalAlpha = label.textColor.a; + this.ctx.fillText(label.text, offset.x - label.size.x/2, offset.y - label.size.y/2); + + this.ctx.globalAlpha = previousAlpha; + } + + /** + * Renders a button + * @param button The button to render + */ + renderButton(button: Button): void { + this.renderLabel(button); + } + + /** + * Renders a slider + * @param slider The slider to render + */ + renderSlider(slider: Slider): void { + // Grab the global alpha so we can adjust it for this render + let previousAlpha = this.ctx.globalAlpha; + this.ctx.globalAlpha = slider.getLayer().getAlpha(); + + // Calcualate the slider size + let sliderSize = new Vec2(slider.size.x, 2); + + // Draw the slider + this.ctx.fillStyle = slider.sliderColor.toString(); + this.ctx.fillRoundedRect(-sliderSize.x/2, -sliderSize.y/2, + sliderSize.x, sliderSize.y, slider.borderRadius); + + // Calculate the nib size and position + let x = MathUtils.lerp(-slider.size.x/2, slider.size.x/2, slider.getValue()); + + // Draw the nib + this.ctx.fillStyle = slider.nibColor.toString(); + this.ctx.fillRoundedRect(x-slider.nibSize.x/2, -slider.nibSize.y/2, + slider.nibSize.x, slider.nibSize.y, slider.borderRadius); + + // Reset the alpha + this.ctx.globalAlpha = previousAlpha; + } + + /** + * Renders a textInput + * @param textInput The textInput to render + */ + renderTextInput(textInput: TextInput): void { + // Show a cursor sometimes + if(textInput.focused && textInput.cursorCounter % 60 > 30){ + textInput.text += "|"; + } + + this.renderLabel(textInput); + + if(textInput.focused){ + if(textInput.cursorCounter % 60 > 30){ + textInput.text = textInput.text.substring(0, textInput.text.length - 1); + } + + textInput.cursorCounter += 1; + if(textInput.cursorCounter >= 60){ + textInput.cursorCounter = 0; + } + } + } + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/RenderingManager.ts b/hw3/src/Wolfie2D/Rendering/RenderingManager.ts new file mode 100644 index 0000000..3f4f91d --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/RenderingManager.ts @@ -0,0 +1,85 @@ +import Map from "../DataTypes/Map"; +import CanvasNode from "../Nodes/CanvasNode"; +import Graphic from "../Nodes/Graphic"; +import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; +import Sprite from "../Nodes/Sprites/Sprite"; +import Tilemap from "../Nodes/Tilemap"; +import UIElement from "../Nodes/UIElement"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import UILayer from "../Scene/Layers/UILayer"; +import Scene from "../Scene/Scene"; +import Color from "../Utils/Color"; + +/** + * An abstract framework to put all rendering in once place in the application + */ +export default abstract class RenderingManager { + /** The ResourceManager */ + protected resourceManager: ResourceManager; + + /** The scene currently being rendered */ + protected scene: Scene; + + constructor(){ + this.resourceManager = ResourceManager.getInstance(); + } + + /** + * Sets the scene currently being rendered + * @param scene The current Scene + */ + setScene(scene: Scene): void { + this.scene = scene; + } + + /** + * Initialize the canvas for the game + * @param canvas The canvas element + * @param width The desired width of the canvas + * @param height The desired height of the canvas + * @returns The rendering context of the canvas + */ + abstract initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): any; + + /** + * Renders the visible set of CanvasNodes and visible portions of tilemaps, as well as any UIElement in UILayers + * @param visibleSet The visible set of CanvasNodes + * @param tilemaps The tilemaps used in the application + * @param uiLayers The user interface layers + */ + abstract render(visibleSet: Array, tilemaps: Array, uiLayers: Map): void; + + /** Clears the canvas */ + abstract clear(color: Color): void; + + /** + * Renders a sprite + * @param sprite The sprite to render + */ + protected abstract renderSprite(sprite: Sprite): void; + + /** + * Renders an animated sprite + * @param sprite The animated sprite to render + */ + protected abstract renderAnimatedSprite(sprite: AnimatedSprite): void; + + /** + * Renders a graphic + * @param graphic The graphic to render + */ + protected abstract renderGraphic(graphic: Graphic): void; + + /** + * Renders a tilemap + * @param tilemap The tilemap to render + */ + protected abstract renderTilemap(tilemap: Tilemap): void; + + + /** + * Renders a UIElement + * @param uiElement The UIElement to render + */ + protected abstract renderUIElement(uiElement: UIElement): void; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRenderer.ts b/hw3/src/Wolfie2D/Rendering/WebGLRenderer.ts new file mode 100644 index 0000000..0ad5a19 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRenderer.ts @@ -0,0 +1,182 @@ +import Graph from "../DataTypes/Graphs/Graph"; +import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import Debug from "../Debug/Debug"; +import CanvasNode from "../Nodes/CanvasNode"; +import Graphic from "../Nodes/Graphic"; +import { GraphicType } from "../Nodes/Graphics/GraphicTypes"; +import Point from "../Nodes/Graphics/Point"; +import Rect from "../Nodes/Graphics/Rect"; +import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite"; +import Sprite from "../Nodes/Sprites/Sprite"; +import Tilemap from "../Nodes/Tilemap"; +import UIElement from "../Nodes/UIElement"; +import Label from "../Nodes/UIElements/Label"; +import ShaderRegistry from "../Registry/Registries/ShaderRegistry"; +import RegistryManager from "../Registry/RegistryManager"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import ParallaxLayer from "../Scene/Layers/ParallaxLayer"; +import UILayer from "../Scene/Layers/UILayer"; +import Color from "../Utils/Color"; +import RenderingUtils from "../Utils/RenderingUtils"; +import RenderingManager from "./RenderingManager"; +import ShaderType from "./WebGLRendering/ShaderType"; + +export default class WebGLRenderer extends RenderingManager { + + protected origin: Vec2; + protected zoom: number; + protected worldSize: Vec2; + + protected gl: WebGLRenderingContext; + protected textCtx: CanvasRenderingContext2D; + + initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): WebGLRenderingContext { + canvas.width = width; + canvas.height = height; + + this.worldSize = Vec2.ZERO; + this.worldSize.x = width; + this.worldSize.y = height; + + // Get the WebGL context + this.gl = canvas.getContext("webgl"); + + this.gl.viewport(0, 0, canvas.width, canvas.height); + + this.gl.disable(this.gl.DEPTH_TEST); + this.gl.enable(this.gl.BLEND); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); + this.gl.enable(this.gl.CULL_FACE); + + // Tell the resource manager we're using WebGL + ResourceManager.getInstance().useWebGL(true, this.gl); + + // Show the text canvas and get its context + let textCanvas = document.getElementById("text-canvas"); + textCanvas.hidden = false; + this.textCtx = textCanvas.getContext("2d"); + + // Size the text canvas to be the same as the game canvas + textCanvas.height = height; + textCanvas.width = width; + + return this.gl; + } + + render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map): void { + for(let node of visibleSet){ + this.renderNode(node); + } + + uiLayers.forEach(key => { + if(!uiLayers.get(key).isHidden()) + uiLayers.get(key).getItems().forEach(node => this.renderNode(node)) + }); + } + + clear(color: Color): void { + this.gl.clearColor(color.r, color.g, color.b, color.a); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + this.textCtx.clearRect(0, 0, this.worldSize.x, this.worldSize.y); + } + + protected renderNode(node: CanvasNode): void { + // Calculate the origin of the viewport according to this sprite + this.origin = this.scene.getViewTranslation(node); + + // Get the zoom level of the scene + this.zoom = this.scene.getViewScale(); + + if(node.hasCustomShader){ + // If the node has a custom shader, render using that + this.renderCustom(node); + } else if(node instanceof Graphic){ + this.renderGraphic(node); + } else if(node instanceof Sprite){ + if(node instanceof AnimatedSprite){ + this.renderAnimatedSprite(node); + } else { + this.renderSprite(node); + } + } else if(node instanceof UIElement){ + this.renderUIElement(node); + } + } + + protected renderSprite(sprite: Sprite): void { + let shader = RegistryManager.shaders.get(ShaderRegistry.SPRITE_SHADER); + let options = this.addOptions(shader.getOptions(sprite), sprite); + shader.render(this.gl, options); + } + + protected renderAnimatedSprite(sprite: AnimatedSprite): void { + let shader = RegistryManager.shaders.get(ShaderRegistry.SPRITE_SHADER); + let options = this.addOptions(shader.getOptions(sprite), sprite); + shader.render(this.gl, options); + } + + protected renderGraphic(graphic: Graphic): void { + + if(graphic instanceof Point){ + let shader = RegistryManager.shaders.get(ShaderRegistry.POINT_SHADER); + let options = this.addOptions(shader.getOptions(graphic), graphic); + shader.render(this.gl, options); + } else if(graphic instanceof Rect) { + let shader = RegistryManager.shaders.get(ShaderRegistry.RECT_SHADER); + let options = this.addOptions(shader.getOptions(graphic), graphic); + shader.render(this.gl, options); + } + } + + protected renderTilemap(tilemap: Tilemap): void { + throw new Error("Method not implemented."); + } + + protected renderUIElement(uiElement: UIElement): void { + if(uiElement instanceof Label){ + let shader = RegistryManager.shaders.get(ShaderRegistry.LABEL_SHADER); + let options = this.addOptions(shader.getOptions(uiElement), uiElement); + shader.render(this.gl, options); + + this.textCtx.setTransform(1, 0, 0, 1, (uiElement.position.x - this.origin.x)*this.zoom, (uiElement.position.y - this.origin.y)*this.zoom); + this.textCtx.rotate(-uiElement.rotation); + let globalAlpha = this.textCtx.globalAlpha; + this.textCtx.globalAlpha = uiElement.alpha; + + // Render text + this.textCtx.font = uiElement.getFontString(); + let offset = uiElement.calculateTextOffset(this.textCtx); + this.textCtx.fillStyle = uiElement.calculateTextColor(); + this.textCtx.globalAlpha = uiElement.textColor.a; + this.textCtx.fillText(uiElement.text, offset.x - uiElement.size.x/2, offset.y - uiElement.size.y/2); + + this.textCtx.globalAlpha = globalAlpha; + this.textCtx.setTransform(1, 0, 0, 1, 0, 0); + } + } + + protected renderCustom(node: CanvasNode): void { + let shader = RegistryManager.shaders.get(node.customShaderKey); + let options = this.addOptions(shader.getOptions(node), node); + shader.render(this.gl, options); + } + + protected addOptions(options: Record, node: CanvasNode): Record { + // Give the shader access to the world size + options.worldSize = this.worldSize; + + // Adjust the origin position to the parallax + let layer = node.getLayer(); + let parallax = new Vec2(1, 1); + if(layer instanceof ParallaxLayer){ + parallax = (layer).parallax; + } + + options.origin = this.origin.clone().mult(parallax); + + return options; + } + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderType.ts new file mode 100644 index 0000000..897dc09 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderType.ts @@ -0,0 +1,44 @@ +import Map from "../../DataTypes/Map"; +import CanvasNode from "../../Nodes/CanvasNode"; +import ResourceManager from "../../ResourceManager/ResourceManager"; + +/** + * A wrapper class for WebGL shaders. + * This class is a singleton, and there is only one for each shader type. + * All objects that use this shader type will refer to and modify this same type. + */ +export default abstract class ShaderType { + /** The name of this shader */ + protected name: string; + + /** The key to the WebGLProgram in the ResourceManager */ + protected programKey: string; + + /** A reference to the resource manager */ + protected resourceManager: ResourceManager; + + constructor(programKey: string){ + this.programKey = programKey; + this.resourceManager = ResourceManager.getInstance(); + } + + /** + * Initializes any buffer objects associated with this shader type. + * @param gl The WebGL rendering context + */ + abstract initBufferObject(): void; + + /** + * Loads any uniforms + * @param gl The WebGL rendering context + * @param options Information about the object we're currently rendering + */ + abstract render(gl: WebGLRenderingContext, options: Record): void; + + /** + * Extracts the options from the CanvasNode and gives them to the render function + * @param node The node to get options from + * @returns An object containing the options that should be passed to the render function + */ + getOptions(node: CanvasNode): Record {return {};} +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts new file mode 100644 index 0000000..7582ec0 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/LabelShaderType.ts @@ -0,0 +1,122 @@ +import Mat4x4 from "../../../DataTypes/Mat4x4"; +import Vec2 from "../../../DataTypes/Vec2"; +import Debug from "../../../Debug/Debug"; +import Rect from "../../../Nodes/Graphics/Rect"; +import Label from "../../../Nodes/UIElements/Label"; +import ResourceManager from "../../../ResourceManager/ResourceManager"; +import QuadShaderType from "./QuadShaderType"; + +export default class LabelShaderType extends QuadShaderType { + + constructor(programKey: string){ + super(programKey); + this.resourceManager = ResourceManager.getInstance(); + } + + initBufferObject(): void { + this.bufferObjectKey = "label"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + render(gl: WebGLRenderingContext, options: Record): void { + const backgroundColor = options.backgroundColor.toWebGL(); + const borderColor = options.borderColor.toWebGL(); + + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + + gl.useProgram(program); + + const vertexData = this.getVertices(options.size.x, options.size.y); + + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + // Attributes + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + // Uniforms + const u_BackgroundColor = gl.getUniformLocation(program, "u_BackgroundColor"); + gl.uniform4fv(u_BackgroundColor, backgroundColor); + + const u_BorderColor = gl.getUniformLocation(program, "u_BorderColor"); + gl.uniform4fv(u_BorderColor, borderColor); + + const u_MaxSize = gl.getUniformLocation(program, "u_MaxSize"); + gl.uniform2f(u_MaxSize, -vertexData[0], vertexData[1]); + + // Get transformation matrix + // We want a square for our rendering space, so get the maximum dimension of our quad + let maxDimension = Math.max(options.size.x, options.size.y); + + const u_BorderWidth = gl.getUniformLocation(program, "u_BorderWidth"); + gl.uniform1f(u_BorderWidth, options.borderWidth/maxDimension); + + const u_BorderRadius = gl.getUniformLocation(program, "u_BorderRadius"); + gl.uniform1f(u_BorderRadius, options.borderRadius/maxDimension); + + // The size of the rendering space will be a square with this maximum dimension + let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y); + + // Center our translations around (0, 0) + const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension; + const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension; + + // Create our transformation matrix + this.translation.translate(new Float32Array([translateX, translateY])); + this.scale.scale(size); + this.rotation.rotate(options.rotation); + let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation); + + // Pass the translation matrix to our shader + const u_Transform = gl.getUniformLocation(program, "u_Transform"); + gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + /** + * The rendering space always has to be a square, so make sure its square w.r.t to the largest dimension + * @param w The width of the quad in pixels + * @param h The height of the quad in pixels + * @returns An array of the vertices of the quad + */ + getVertices(w: number, h: number): Float32Array { + let x, y; + + if(h > w){ + y = 0.5; + x = w/(2*h); + } else { + x = 0.5; + y = h/(2*w); + } + + return new Float32Array([ + -x, y, + -x, -y, + x, y, + x, -y + ]); + } + + getOptions(rect: Label): Record { + let options: Record = { + position: rect.position, + backgroundColor: rect.calculateBackgroundColor(), + borderColor: rect.calculateBorderColor(), + borderWidth: rect.borderWidth, + borderRadius: rect.borderRadius, + size: rect.size, + rotation: rect.rotation + } + + return options; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/PointShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/PointShaderType.ts new file mode 100644 index 0000000..e7ae466 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/PointShaderType.ts @@ -0,0 +1,61 @@ +import Debug from "../../../Debug/Debug"; +import Point from "../../../Nodes/Graphics/Point"; +import ResourceManager from "../../../ResourceManager/ResourceManager"; +import RenderingUtils from "../../../Utils/RenderingUtils"; +import ShaderType from "../ShaderType"; + +export default class PointShaderType extends ShaderType { + + protected bufferObjectKey: string; + + constructor(programKey: string){ + super(programKey); + } + + initBufferObject(): void { + this.bufferObjectKey = "point"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + render(gl: WebGLRenderingContext, options: Record): void { + let position = RenderingUtils.toWebGLCoords(options.position, options.origin, options.worldSize); + let color = RenderingUtils.toWebGLColor(options.color); + + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + + gl.useProgram(program); + + const vertexData = position; + + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + // Attributes + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + // Uniforms + const u_Color = gl.getUniformLocation(program, "u_Color"); + gl.uniform4fv(u_Color, color); + + const u_PointSize = gl.getUniformLocation(program, "u_PointSize"); + gl.uniform1f(u_PointSize, options.pointSize); + + gl.drawArrays(gl.POINTS, 0, 1); + } + + getOptions(point: Point): Record { + let options: Record = { + position: point.position, + color: point.color, + pointSize: point.size, + } + + return options; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/QuadShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/QuadShaderType.ts new file mode 100644 index 0000000..fa524f1 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/QuadShaderType.ts @@ -0,0 +1,25 @@ +import Mat4x4 from "../../../DataTypes/Mat4x4"; +import ShaderType from "../ShaderType"; + +/** Represents any WebGL objects that have a quad mesh (i.e. a rectangular game object composed of only two triangles) */ +export default abstract class QuadShaderType extends ShaderType { + /** The key to the buffer object for this shader */ + protected bufferObjectKey: string; + + /** The scale matric */ + protected scale: Mat4x4; + + /** The rotation matrix */ + protected rotation: Mat4x4; + + /** The translation matrix */ + protected translation: Mat4x4; + + constructor(programKey: string){ + super(programKey); + + this.scale = Mat4x4.IDENTITY; + this.rotation = Mat4x4.IDENTITY; + this.translation = Mat4x4.IDENTITY; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType.ts new file mode 100644 index 0000000..e977434 --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType.ts @@ -0,0 +1,133 @@ +import Mat4x4 from "../../../DataTypes/Mat4x4"; +import Vec2 from "../../../DataTypes/Vec2"; +import Rect from "../../../Nodes/Graphics/Rect"; +import ResourceManager from "../../../ResourceManager/ResourceManager"; +import QuadShaderType from "./QuadShaderType"; + +export default class RectShaderType extends QuadShaderType { + + constructor(programKey: string){ + super(programKey); + this.resourceManager = ResourceManager.getInstance(); + } + + initBufferObject(): void { + this.bufferObjectKey = "rect"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + render(gl: WebGLRenderingContext, options: Record): void { + const color = options.color.toWebGL(); + + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + + gl.useProgram(program); + + const vertexData = this.getVertices(options.size.x, options.size.y); + + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + // Attributes + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + // Uniforms + const u_Color = gl.getUniformLocation(program, "u_Color"); + gl.uniform4fv(u_Color, color); + + // Get transformation matrix + // We want a square for our rendering space, so get the maximum dimension of our quad + let maxDimension = Math.max(options.size.x, options.size.y); + + // The size of the rendering space will be a square with this maximum dimension + let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y); + + // Center our translations around (0, 0) + const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension; + const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension; + + // Create our transformation matrix + this.translation.translate(new Float32Array([translateX, translateY])); + this.scale.scale(size); + this.rotation.rotate(options.rotation); + let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation); + + // Pass the translation matrix to our shader + const u_Transform = gl.getUniformLocation(program, "u_Transform"); + gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + + /* + So as it turns out, WebGL has an issue with non-square quads. + It doesn't like when you don't have a 1-1 scale, and rotations are entirely messed up if this is not the case. + To solve this, I used the scale of the LARGEST dimension of the quad to make a square, then adjusted the vertex coordinates inside of that. + A diagram of the solution follows. + + There is a bounding square for the quad with dimensions hxh (in this case, since height is the largest dimension). + The offset in the vertical direction is therefore 0.5, as it is normally. + However, the offset in the horizontal direction is not so straightforward, but isn't conceptually hard. + All we really have to do is a range change from [0, height/2] to [0, 0.5], where our value is t = width/2, and 0 <= t <= height/2. + + So now we have our rect, in a space scaled with respect to the largest dimension. + Rotations work as you would expect, even for long rectangles. + + 0.5 + __ __ __ __ __ __ __ + | |88888888888| | + | |88888888888| | + | |88888888888| | + -0.5|_ _|88888888888|_ _|0.5 + | |88888888888| | + | |88888888888| | + | |88888888888| | + |___|88888888888|___| + -0.5 + + The getVertices function below does as described, and converts the range + */ + /** + * The rendering space always has to be a square, so make sure its square w.r.t to the largest dimension + * @param w The width of the quad in pixels + * @param h The height of the quad in pixels + * @returns An array of the vertices of the quad + */ + getVertices(w: number, h: number): Float32Array { + let x, y; + + if(h > w){ + y = 0.5; + x = w/(2*h); + } else { + x = 0.5; + y = h/(2*w); + } + + return new Float32Array([ + -x, y, + -x, -y, + x, y, + x, -y + ]); + } + + getOptions(rect: Rect): Record { + let options: Record = { + position: rect.position, + color: rect.color, + size: rect.size, + rotation: rect.rotation + } + + return options; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts new file mode 100644 index 0000000..511d39a --- /dev/null +++ b/hw3/src/Wolfie2D/Rendering/WebGLRendering/ShaderTypes/SpriteShaderType.ts @@ -0,0 +1,138 @@ +import Mat4x4 from "../../../DataTypes/Mat4x4"; +import Vec2 from "../../../DataTypes/Vec2"; +import Debug from "../../../Debug/Debug"; +import AnimatedSprite from "../../../Nodes/Sprites/AnimatedSprite"; +import Sprite from "../../../Nodes/Sprites/Sprite"; +import ResourceManager from "../../../ResourceManager/ResourceManager"; +import QuadShaderType from "./QuadShaderType"; + +/** A shader for sprites and animated sprites */ +export default class SpriteShaderType extends QuadShaderType { + constructor(programKey: string){ + super(programKey); + this.resourceManager = ResourceManager.getInstance(); + } + + initBufferObject(): void { + this.bufferObjectKey = "sprite"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + render(gl: WebGLRenderingContext, options: Record): void { + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + const texture = this.resourceManager.getTexture(options.imageKey); + + gl.useProgram(program); + + const vertexData = this.getVertices(options.size.x, options.size.y, options.scale); + + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + // Attributes + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 4 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + const a_TexCoord = gl.getAttribLocation(program, "a_TexCoord"); + gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * FSIZE, 2*FSIZE); + gl.enableVertexAttribArray(a_TexCoord); + + // Uniforms + // Get transformation matrix + // We want a square for our rendering space, so get the maximum dimension of our quad + let maxDimension = Math.max(options.size.x, options.size.y); + + // The size of the rendering space will be a square with this maximum dimension + let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y); + + // Center our translations around (0, 0) + const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension; + const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension; + + // Create our transformation matrix + this.translation.translate(new Float32Array([translateX, translateY])); + this.scale.scale(size); + this.rotation.rotate(options.rotation); + let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation); + + // Pass the translation matrix to our shader + const u_Transform = gl.getUniformLocation(program, "u_Transform"); + gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); + + // Set up our sampler with our assigned texture unit + const u_Sampler = gl.getUniformLocation(program, "u_Sampler"); + gl.uniform1i(u_Sampler, texture); + + // Pass in texShift + const u_texShift = gl.getUniformLocation(program, "u_texShift"); + gl.uniform2fv(u_texShift, options.texShift); + + // Pass in texScale + const u_texScale = gl.getUniformLocation(program, "u_texScale"); + gl.uniform2fv(u_texScale, options.texScale); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + /** + * The rendering space always has to be a square, so make sure its square w.r.t to the largest dimension + * @param w The width of the quad in pixels + * @param h The height of the quad in pixels + * @returns An array of the vertices of the quad + */ + getVertices(w: number, h: number, scale: Float32Array): Float32Array { + let x, y; + + if(h > w){ + y = 0.5; + x = w/(2*h); + } else { + x = 0.5; + y = h/(2*w); + } + + // Scale the rendering space if needed + x *= scale[0]; + y *= scale[1]; + + return new Float32Array([ + -x, y, 0.0, 0.0, + -x, -y, 0.0, 1.0, + x, y, 1.0, 0.0, + x, -y, 1.0, 1.0 + ]); + } + + getOptions(sprite: Sprite): Record { + let texShift; + let texScale; + + if(sprite instanceof AnimatedSprite){ + let animationIndex = sprite.animation.getIndexAndAdvanceAnimation(); + let offset = sprite.getAnimationOffset(animationIndex); + texShift = new Float32Array([offset.x / (sprite.cols * sprite.size.x), offset.y / (sprite.rows * sprite.size.y)]); + texScale = new Float32Array([1/(sprite.cols), 1/(sprite.rows)]); + } else { + texShift = new Float32Array([0, 0]); + texScale = new Float32Array([1, 1]); + } + + let options: Record = { + position: sprite.position, + rotation: sprite.rotation, + size: sprite.size, + scale: sprite.scale.toArray(), + imageKey: sprite.imageId, + texShift, + texScale + } + + return options; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/ResourceManager/ResourceManager.ts b/hw3/src/Wolfie2D/ResourceManager/ResourceManager.ts new file mode 100644 index 0000000..501495a --- /dev/null +++ b/hw3/src/Wolfie2D/ResourceManager/ResourceManager.ts @@ -0,0 +1,1035 @@ +import Map from "../DataTypes/Map"; +import Queue from "../DataTypes/Queue"; +import { TiledTilemapData } from "../DataTypes/Tilesets/TiledData"; +import StringUtils from "../Utils/StringUtils"; +import AudioManager from "../Sound/AudioManager"; +import Spritesheet from "../DataTypes/Spritesheet"; +import WebGLProgramType from "../DataTypes/Rendering/WebGLProgramType"; + +/** + * The resource manager for the game engine. + * The resource manager interfaces with the loadable assets of a game such as images, data files, + * and sounds, which are all found in the dist folder. + * This class controls loading and updates the @reference[Scene] with the loading progress, so that the scene does + * not start before all necessary assets are loaded. + */ +export default class ResourceManager { + // Instance for the singleton class + private static instance: ResourceManager; + + // Booleans to keep track of whether or not the ResourceManager is currently loading something + /** Whether or not any resources are loading */ + private loading: boolean; + /** A boolean to indicate that the assets just finished loading */ + private justLoaded: boolean; + + // Functions to do something when loading progresses or is completed such as render a loading screen + /** A function that is called when loading progresses */ + public onLoadProgress: Function; + /** A function that is called when loading completes */ + public onLoadComplete: Function; + + + /** Number to keep track of how many images need to be loaded*/ + private loadonly_imagesLoaded: number; + /** Number to keep track of how many images are loaded */ + private loadonly_imagesToLoad: number; + /** The queue of images we must load */ + private loadonly_imageLoadingQueue: Queue; + /** A map of the images that are currently loaded and being used by the scene. The reference to these images only exist here for easy cleanup. */ + private images: Map; + + /** Number to keep track of how many tilemaps need to be loaded */ + private loadonly_spritesheetsLoaded: number; + /** Number to keep track of how many tilemaps are loaded */ + private loadonly_spritesheetsToLoad: number; + /** The queue of tilemaps we must load */ + private loadonly_spritesheetLoadingQueue: Queue; + /** A map of the tilemaps that are currently loaded and (presumably) being used by the scene */ + private spritesheets: Map; + + /** Number to keep track of how many tilemaps need to be loaded */ + private loadonly_tilemapsLoaded: number; + /** Number to keep track of how many tilemaps are loaded */ + private loadonly_tilemapsToLoad: number; + /** The queue of tilemaps we must load */ + private loadonly_tilemapLoadingQueue: Queue; + /** A map of the tilemaps that are currently loaded and (presumably) being used by the scene */ + private tilemaps: Map; + + /** Number to keep track of how many sounds need to be loaded */ + private loadonly_audioLoaded: number; + /** Number to keep track of how many sounds are loaded */ + private loadonly_audioToLoad: number; + /** The queue of sounds we must load */ + private loadonly_audioLoadingQueue: Queue; + /** A map of the sounds that are currently loaded and (presumably) being used by the scene */ + private audioBuffers: Map; + + /** The total number of "types" of things that need to be loaded (i.e. images and tilemaps) */ + private loadonly_typesToLoad: number; + + private loadonly_jsonLoaded: number; + private loadonly_jsonToLoad: number; + private loadonly_jsonLoadingQueue: Queue; + private jsonObjects: Map>; + + /* ########## INFORMATION SPECIAL TO WEBGL ########## */ + private gl_WebGLActive: boolean; + + private loadonly_gl_ShaderProgramsLoaded: number; + private loadonly_gl_ShaderProgramsToLoad: number; + private loadonly_gl_ShaderLoadingQueue: Queue; + + private gl_ShaderPrograms: Map; + + private gl_Textures: Map; + private gl_NextTextureID: number; + private gl_Buffers: Map; + + private gl: WebGLRenderingContext; + + /* ########## UNLOADING AND EXCLUSION LIST ########## */ + /** A list of resources that will be unloaded at the end of the current scene */ + private resourcesToUnload: Array; + + /** A list of resources to keep until further notice */ + private resourcesToKeep: Array; + + private constructor(){ + this.loading = false; + this.justLoaded = false; + + this.loadonly_imagesLoaded = 0; + this.loadonly_imagesToLoad = 0; + this.loadonly_imageLoadingQueue = new Queue(); + this.images = new Map(); + + this.loadonly_spritesheetsLoaded = 0; + this.loadonly_spritesheetsToLoad = 0; + this.loadonly_spritesheetLoadingQueue = new Queue(); + this.spritesheets = new Map(); + + this.loadonly_tilemapsLoaded = 0; + this.loadonly_tilemapsToLoad = 0; + this.loadonly_tilemapLoadingQueue = new Queue(); + this.tilemaps = new Map(); + + this.loadonly_audioLoaded = 0; + this.loadonly_audioToLoad = 0; + this.loadonly_audioLoadingQueue = new Queue(); + this.audioBuffers = new Map(); + + this.loadonly_jsonLoaded = 0; + this.loadonly_jsonToLoad = 0; + this.loadonly_jsonLoadingQueue = new Queue(); + this.jsonObjects = new Map(); + + this.loadonly_gl_ShaderProgramsLoaded = 0; + this.loadonly_gl_ShaderProgramsToLoad = 0; + this.loadonly_gl_ShaderLoadingQueue = new Queue(); + + this.gl_ShaderPrograms = new Map(); + + this.gl_Textures = new Map(); + this.gl_NextTextureID = 0; + this.gl_Buffers = new Map(); + + this.resourcesToUnload = new Array(); + this.resourcesToKeep = new Array(); + }; + + /* ######################################## SINGLETON ########################################*/ + /** + * Returns the current instance of this class or a new instance if none exist + * @returns The resource manager + */ + static getInstance(): ResourceManager { + if(!this.instance){ + this.instance = new ResourceManager(); + } + + return this.instance; + } + + /* ######################################## PUBLIC FUNCTION ########################################*/ + /** + * Activates or deactivates the use of WebGL + * @param flag True if WebGL should be used, false otherwise + * @param gl The instance of the graphics context, if applicable + */ + public useWebGL(flag: boolean, gl: WebGLRenderingContext): void { + this.gl_WebGLActive = flag; + + if(this.gl_WebGLActive){ + this.gl = gl; + } + } + + /** + * Loads an image from file + * @param key The key to associate the loaded image with + * @param path The path to the image to load + */ + public image(key: string, path: string): void { + this.loadonly_imageLoadingQueue.enqueue({key: key, path: path}); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepImage(key: string): void { + this.keepResource(key, ResourceType.IMAGE); + } + + /** + * Retrieves a loaded image + * @param key The key of the loaded image + * @returns The image element associated with this key + */ + public getImage(key: string): HTMLImageElement { + let image = this.images.get(key); + if(image === undefined){ + throw `There is no image associated with key "${key}"` + } + return image; + } + + /** + * Loads a spritesheet from file + * @param key The key to associate the loaded spritesheet with + * @param path The path to the spritesheet to load + */ + public spritesheet(key: string, path: string): void { + this.loadonly_spritesheetLoadingQueue.enqueue({key: key, path: path}); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepSpritesheet(key: string): void { + this.keepResource(key, ResourceType.SPRITESHEET); + } + + /** + * Retrieves a loaded spritesheet + * @param key The key of the spritesheet to load + * @returns The loaded Spritesheet + */ + public getSpritesheet(key: string): Spritesheet { + return this.spritesheets.get(key); + } + + /** + * Loads an audio file + * @param key The key to associate with the loaded audio file + * @param path The path to the audio file to load + */ + public audio(key: string, path: string): void { + this.loadonly_audioLoadingQueue.enqueue({key: key, path: path}); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepAudio(key: string): void { + this.keepResource(key, ResourceType.AUDIO); + } + + /** + * Retrieves a loaded audio file + * @param key The key of the audio file to load + * @returns The AudioBuffer created from the loaded audio fle + */ + public getAudio(key: string): AudioBuffer { + return this.audioBuffers.get(key); + } + + /** + * Load a tilemap from a json file. Automatically loads related images + * @param key The key to associate with the loaded tilemap + * @param path The path to the tilemap to load + */ + public tilemap(key: string, path: string): void { + this.loadonly_tilemapLoadingQueue.enqueue({key: key, path: path}); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepTilemap(key: string): void { + this.keepResource(key, ResourceType.TILEMAP); + } + + /** + * Retreives a loaded tilemap + * @param key The key of the loaded tilemap + * @returns The tilemap data associated with the key + */ + public getTilemap(key: string): TiledTilemapData { + return this.tilemaps.get(key); + } + + /** + * Loads an object from a json file. + * @param key The key to associate with the loaded object + * @param path The path to the json file to load + */ + public object(key: string, path: string){ + this.loadonly_jsonLoadingQueue.enqueue({key: key, path: path}); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepObject(key: string): void { + this.keepResource(key, ResourceType.JSON); + } + + /** + * Retreives a loaded object + * @param key The key of the loaded object + * @returns The object data associated with the key + */ + public getObject(key: string){ + return this.jsonObjects.get(key); + } + + /* ######################################## LOAD FUNCTION ########################################*/ + /** + * Loads all resources currently in the queue + * @param callback The function to cal when the resources are finished loading + */ + loadResourcesFromQueue(callback: Function): void { + this.loadonly_typesToLoad = 5; + + this.loading = true; + + // Load everything in the queues. Tilemaps have to come before images because they will add new images to the queue + this.loadTilemapsFromQueue(() => { + console.log("Loaded Tilemaps"); + this.loadSpritesheetsFromQueue(() => { + console.log("Loaded Spritesheets"); + this.loadImagesFromQueue(() => { + console.log("Loaded Images"); + this.loadAudioFromQueue(() => { + console.log("Loaded Audio"); + this.loadObjectsFromQueue(() => { + console.log("Loaded Objects"); + + if(this.gl_WebGLActive){ + this.gl_LoadShadersFromQueue(() => { + console.log("Loaded Shaders"); + this.finishLoading(callback); + }); + } else { + this.finishLoading(callback); + } + }) + }); + }); + }); + }); + } + + private finishLoading(callback: Function): void { + // Done loading + this.loading = false; + this.justLoaded = true; + callback(); + } + + /* ######################################## UNLOAD FUNCTION ########################################*/ + + private keepResource(key: string, type: ResourceType): void { + console.log("Keep resource..."); + for(let i = 0; i < this.resourcesToUnload.length; i++){ + let resource = this.resourcesToUnload[i]; + if(resource.key === key && resource.resourceType === type){ + console.log("Found resource " + key + " of type " + type + ". Keeping."); + let resourceToMove = this.resourcesToUnload.splice(i, 1); + this.resourcesToKeep.push(...resourceToMove); + return; + } + } + } + + /** + * Deletes references to all resources in the resource manager + */ + unloadAllResources(): void { + this.loading = false; + this.justLoaded = false; + + for(let resource of this.resourcesToUnload){ + // Unload the resource + this.unloadResource(resource); + } + } + + private unloadResource(resource: ResourceReference): void { + // Delete the resource itself + switch(resource.resourceType){ + case ResourceType.IMAGE: + this.images.delete(resource.key); + if(this.gl_WebGLActive){ + this.gl_Textures.delete(resource.key); + } + break; + case ResourceType.TILEMAP: + this.tilemaps.delete(resource.key); + break; + case ResourceType.SPRITESHEET: + this.spritesheets.delete(resource.key); + break; + case ResourceType.AUDIO: + this.audioBuffers.delete(resource.key); + break; + case ResourceType.JSON: + this.jsonObjects.delete(resource.key); + break; + /*case ResourceType.SHADER: + this.gl_ShaderPrograms.get(resource.key).delete(this.gl); + this.gl_ShaderPrograms.delete(resource.key); + break;*/ + } + + // Delete any dependencies + for(let dependency of resource.dependencies){ + this.unloadResource(dependency); + } + } + + /* ######################################## WORK FUNCTIONS ########################################*/ + /** + * Loads all tilemaps currently in the tilemap loading queue + * @param onFinishLoading The function to call when loading is complete + */ + private loadTilemapsFromQueue(onFinishLoading: Function): void { + this.loadonly_tilemapsToLoad = this.loadonly_tilemapLoadingQueue.getSize(); + this.loadonly_tilemapsLoaded = 0; + + // If no items to load, we're finished + if(this.loadonly_tilemapsToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_tilemapLoadingQueue.hasItems()){ + let tilemap = this.loadonly_tilemapLoadingQueue.dequeue(); + this.loadTilemap(tilemap.key, tilemap.path, onFinishLoading); + } + } + + /** + * Loads a singular tilemap + * @param key The key of the tilemap + * @param pathToTilemapJSON The path to the tilemap JSON file + * @param callbackIfLast The function to call if this is the last tilemap to load + */ + private loadTilemap(key: string, pathToTilemapJSON: string, callbackIfLast: Function): void { + this.loadTextFile(pathToTilemapJSON, (fileText: string) => { + let tilemapObject = JSON.parse(fileText); + + // We can parse the object later - it's much faster than loading + this.tilemaps.add(key, tilemapObject); + let resource = new ResourceReference(key, ResourceType.TILEMAP); + + // Grab the tileset images we need to load and add them to the imageloading queue + for(let tileset of tilemapObject.tilesets){ + if(tileset.image){ + let key = tileset.image; + let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key; + this.loadonly_imageLoadingQueue.enqueue({key: key, path: path, isDependency: true}); + + // Add this image as a dependency to the tilemap + resource.addDependency(new ResourceReference(key, ResourceType.IMAGE)); + } else if(tileset.tiles){ + for(let tile of tileset.tiles){ + let key = tile.image; + let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key; + this.loadonly_imageLoadingQueue.enqueue({key: key, path: path, isDependency: true}); + + // Add this image as a dependency to the tilemap + resource.addDependency(new ResourceReference(key, ResourceType.IMAGE)); + } + } + } + + // Add the resource reference to the list of resource to unload + this.resourcesToUnload.push(resource); + + // Finish loading + this.finishLoadingTilemap(callbackIfLast); + }); + } + + /** + * Finish loading a tilemap. Calls the callback function if this is the last tilemap being loaded + * @param callback The function to call if this is the last tilemap to load + */ + private finishLoadingTilemap(callback: Function): void { + this.loadonly_tilemapsLoaded += 1; + + if(this.loadonly_tilemapsLoaded === this.loadonly_tilemapsToLoad){ + // We're done loading tilemaps + callback(); + } + } + + /** + * Loads all spritesheets currently in the spritesheet loading queue + * @param onFinishLoading The function to call when the spritesheets are done loading + */ + private loadSpritesheetsFromQueue(onFinishLoading: Function): void { + this.loadonly_spritesheetsToLoad = this.loadonly_spritesheetLoadingQueue.getSize(); + this.loadonly_spritesheetsLoaded = 0; + + // If no items to load, we're finished + if(this.loadonly_spritesheetsToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_spritesheetLoadingQueue.hasItems()){ + let spritesheet = this.loadonly_spritesheetLoadingQueue.dequeue(); + this.loadSpritesheet(spritesheet.key, spritesheet.path, onFinishLoading); + } + } + + /** + * Loads a singular spritesheet + * @param key The key of the spritesheet to load + * @param pathToSpritesheetJSON The path to the spritesheet JSON file + * @param callbackIfLast The function to call if this is the last spritesheet + */ + private loadSpritesheet(key: string, pathToSpritesheetJSON: string, callbackIfLast: Function): void { + this.loadTextFile(pathToSpritesheetJSON, (fileText: string) => { + let spritesheet = JSON.parse(fileText); + + // We can parse the object later - it's much faster than loading + this.spritesheets.add(key, spritesheet); + + let resource = new ResourceReference(key, ResourceType.SPRITESHEET); + + // Grab the image we need to load and add it to the imageloading queue + let path = StringUtils.getPathFromFilePath(pathToSpritesheetJSON) + spritesheet.spriteSheetImage; + this.loadonly_imageLoadingQueue.enqueue({key: spritesheet.name, path: path, isDependency: true}); + + resource.addDependency(new ResourceReference(spritesheet.name, ResourceType.IMAGE)); + this.resourcesToUnload.push(resource); + + // Finish loading + this.finishLoadingSpritesheet(callbackIfLast); + }); + } + + /** + * Finish loading a spritesheet. Calls the callback function if this is the last spritesheet being loaded + * @param callback The function to call if this is the last spritesheet to load + */ + private finishLoadingSpritesheet(callback: Function): void { + this.loadonly_spritesheetsLoaded += 1; + + if(this.loadonly_spritesheetsLoaded === this.loadonly_spritesheetsToLoad){ + // We're done loading spritesheets + callback(); + } + } + + /** + * Loads all images currently in the image loading queue + * @param onFinishLoading The function to call when there are no more images to load + */ + private loadImagesFromQueue(onFinishLoading: Function): void { + this.loadonly_imagesToLoad = this.loadonly_imageLoadingQueue.getSize(); + this.loadonly_imagesLoaded = 0; + + // If no items to load, we're finished + if(this.loadonly_imagesToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_imageLoadingQueue.hasItems()){ + let image = this.loadonly_imageLoadingQueue.dequeue(); + this.loadImage(image.key, image.path, image.isDependency, onFinishLoading); + } + } + + /** + * Loads a singular image + * @param key The key of the image to load + * @param path The path to the image to load + * @param callbackIfLast The function to call if this is the last image + */ + public loadImage(key: string, path: string, isDependency: boolean, callbackIfLast: Function): void { + var image = new Image(); + + image.onload = () => { + // Add to loaded images + this.images.add(key, image); + + // If not a dependency, push it to the unload list. Otherwise it's managed by something else + if(!isDependency){ + this.resourcesToUnload.push(new ResourceReference(key, ResourceType.IMAGE)); + } + + // If WebGL is active, create a texture + if(this.gl_WebGLActive){ + this.createWebGLTexture(key, image); + } + + // Finish image load + this.finishLoadingImage(callbackIfLast); + } + + image.src = path; + } + + /** + * Finish loading an image. If this is the last image, it calls the callback function + * @param callback The function to call if this is the last image + */ + private finishLoadingImage(callback: Function): void { + this.loadonly_imagesLoaded += 1; + + if(this.loadonly_imagesLoaded === this.loadonly_imagesToLoad ){ + // We're done loading images + callback(); + } + } + + /** + * Loads all audio currently in the tilemap loading queue + * @param onFinishLoading The function to call when tilemaps are done loading + */ + private loadAudioFromQueue(onFinishLoading: Function){ + this.loadonly_audioToLoad = this.loadonly_audioLoadingQueue.getSize(); + this.loadonly_audioLoaded = 0; + + // If no items to load, we're finished + if(this.loadonly_audioToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_audioLoadingQueue.hasItems()){ + let audio = this.loadonly_audioLoadingQueue.dequeue(); + this.loadAudio(audio.key, audio.path, onFinishLoading); + } + } + + /** + * Load a singular audio file + * @param key The key to the audio file to load + * @param path The path to the audio file to load + * @param callbackIfLast The function to call if this is the last audio file to load + */ + private loadAudio(key: string, path: string, callbackIfLast: Function): void { + let audioCtx = AudioManager.getInstance().getAudioContext(); + + let request = new XMLHttpRequest(); + request.open('GET', path, true); + request.responseType = 'arraybuffer'; + + request.onload = () => { + audioCtx.decodeAudioData(request.response, (buffer) => { + // Add to list of audio buffers + this.audioBuffers.add(key, buffer); + this.resourcesToUnload.push(new ResourceReference(key, ResourceType.AUDIO)); + + // Finish loading sound + this.finishLoadingAudio(callbackIfLast); + }, (error) =>{ + throw "Error loading sound"; + }); + } + request.send(); + } + + /** + * Finish loading an audio file. Calls the callback functon if this is the last audio sample being loaded. + * @param callback The function to call if this is the last audio file to load + */ + private finishLoadingAudio(callback: Function): void { + this.loadonly_audioLoaded += 1; + + if(this.loadonly_audioLoaded === this.loadonly_audioToLoad){ + // We're done loading audio + callback(); + } + } + + /** + * Loads all objects currently in the object loading queue + * @param onFinishLoading The function to call when there are no more objects to load + */ + private loadObjectsFromQueue(onFinishLoading: Function): void { + this.loadonly_jsonToLoad = this.loadonly_jsonLoadingQueue.getSize(); + this.loadonly_jsonLoaded = 0; + + // If no items to load, we're finished + if(this.loadonly_jsonToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_jsonLoadingQueue.hasItems()){ + let obj = this.loadonly_jsonLoadingQueue.dequeue(); + this.loadObject(obj.key, obj.path, onFinishLoading); + } + } + + /** + * Loads a singular object + * @param key The key of the object to load + * @param path The path to the object to load + * @param callbackIfLast The function to call if this is the last object + */ + public loadObject(key: string, path: string, callbackIfLast: Function): void { + this.loadTextFile(path, (fileText: string) => { + let obj = JSON.parse(fileText); + this.jsonObjects.add(key, obj); + + this.resourcesToUnload.push(new ResourceReference(key, ResourceType.JSON)); + + this.finishLoadingObject(callbackIfLast); + }); + } + + /** + * Finish loading an object. If this is the last object, it calls the callback function + * @param callback The function to call if this is the last object + */ + private finishLoadingObject(callback: Function): void { + this.loadonly_jsonLoaded += 1; + + if(this.loadonly_jsonLoaded === this.loadonly_jsonToLoad){ + // We're done loading objects + callback(); + } + } + + /* ########## WEBGL SPECIFIC FUNCTIONS ########## */ + + public getTexture(key: string): number { + return this.gl_Textures.get(key); + } + + public getShaderProgram(key: string): WebGLProgram { + return this.gl_ShaderPrograms.get(key).program; + } + + public getBuffer(key: string): WebGLBuffer { + return this.gl_Buffers.get(key); + } + + private createWebGLTexture(imageKey: string, image: HTMLImageElement): void { + // Get the texture ID + const textureID = this.getTextureID(this.gl_NextTextureID); + + // Create the texture + const texture = this.gl.createTexture(); + + // Set up the texture + // Enable texture0 + this.gl.activeTexture(textureID); + + // Bind our texture to texture 0 + this.gl.bindTexture(this.gl.TEXTURE_2D, texture); + + // Set the texture parameters + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); + this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); + + // Set the texture image + this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image); + + // Add the texture to our map with the same key as the image + this.gl_Textures.add(imageKey, this.gl_NextTextureID); + + // Increment the key + this.gl_NextTextureID += 1; + } + + private getTextureID(id: number): number { + // Start with 9 cases - this can be expanded if needed, but for the best performance, + // Textures should be stitched into an atlas + switch(id){ + case 0: return this.gl.TEXTURE0; + case 1: return this.gl.TEXTURE1; + case 2: return this.gl.TEXTURE2; + case 3: return this.gl.TEXTURE3; + case 4: return this.gl.TEXTURE4; + case 5: return this.gl.TEXTURE5; + case 6: return this.gl.TEXTURE6; + case 7: return this.gl.TEXTURE7; + case 8: return this.gl.TEXTURE8; + default: return this.gl.TEXTURE9; + } + } + + public createBuffer(key: string): void { + if(this.gl_WebGLActive){ + let buffer = this.gl.createBuffer(); + + this.gl_Buffers.add(key, buffer); + } + } + + /** + * Enqueues loading of a new shader program + * @param key The key of the shader program + * @param vShaderFilepath + * @param fShaderFilepath + */ + public shader(key: string, vShaderFilepath: string, fShaderFilepath: string): void { + let splitPath = vShaderFilepath.split("."); + let end = splitPath[splitPath.length - 1]; + + if(end !== "vshader"){ + throw `${vShaderFilepath} is not a valid vertex shader - must end in ".vshader`; + } + + splitPath = fShaderFilepath.split("."); + end = splitPath[splitPath.length - 1]; + + if(end !== "fshader"){ + throw `${fShaderFilepath} is not a valid vertex shader - must end in ".fshader`; + } + + let paths = new KeyPath_Shader(); + paths.key = key; + paths.vpath = vShaderFilepath; + paths.fpath = fShaderFilepath; + + this.loadonly_gl_ShaderLoadingQueue.enqueue(paths); + } + + /** + * Tells the resource manager to keep this resource + * @param key The key of the resource + */ + public keepShader(key: string): void { + this.keepResource(key, ResourceType.IMAGE); + } + + private gl_LoadShadersFromQueue(onFinishLoading: Function): void { + this.loadonly_gl_ShaderProgramsToLoad = this.loadonly_gl_ShaderLoadingQueue.getSize(); + this.loadonly_gl_ShaderProgramsLoaded = 0; + + // If webGL isn'active or there are no items to load, we're finished + if(!this.gl_WebGLActive || this.loadonly_gl_ShaderProgramsToLoad === 0){ + onFinishLoading(); + return; + } + + while(this.loadonly_gl_ShaderLoadingQueue.hasItems()){ + let shader = this.loadonly_gl_ShaderLoadingQueue.dequeue(); + this.gl_LoadShader(shader.key, shader.vpath, shader.fpath, onFinishLoading); + } + } + + private gl_LoadShader(key: string, vpath: string, fpath: string, callbackIfLast: Function): void { + this.loadTextFile(vpath, (vFileText: string) => { + const vShader = vFileText; + + this.loadTextFile(fpath, (fFileText: string) => { + const fShader = fFileText + + // Extract the program and shaders + const [shaderProgram, vertexShader, fragmentShader] = this.createShaderProgram(vShader, fShader); + + // Create a wrapper type + const programWrapper = new WebGLProgramType(); + programWrapper.program = shaderProgram; + programWrapper.vertexShader = vertexShader; + programWrapper.fragmentShader = fragmentShader; + + // Add to our map + this.gl_ShaderPrograms.add(key, programWrapper); + + this.resourcesToUnload.push(new ResourceReference(key, ResourceType.SHADER)); + + // Finish loading + this.gl_FinishLoadingShader(callbackIfLast); + }); + }); + } + + private gl_FinishLoadingShader(callback: Function): void { + this.loadonly_gl_ShaderProgramsLoaded += 1; + + if(this.loadonly_gl_ShaderProgramsLoaded === this.loadonly_gl_ShaderProgramsToLoad){ + // We're done loading shaders + callback(); + } + } + + private createShaderProgram(vShaderSource: string, fShaderSource: string){ + const vertexShader = this.loadVertexShader(vShaderSource); + const fragmentShader = this.loadFragmentShader(fShaderSource); + + if(vertexShader === null || fragmentShader === null){ + // We had a problem intializing - error + return null; + } + + // Create a shader program + const program = this.gl.createProgram(); + if(!program) { + // Error creating + console.warn("Failed to create program"); + return null; + } + + // Attach our vertex and fragment shader + this.gl.attachShader(program, vertexShader); + this.gl.attachShader(program, fragmentShader); + + // Link + this.gl.linkProgram(program); + if(!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){ + // Error linking + const error = this.gl.getProgramInfoLog(program); + console.warn("Failed to link program: " + error); + + // Clean up + this.gl.deleteProgram(program); + this.gl.deleteShader(vertexShader); + this.gl.deleteShader(fragmentShader); + return null; + } + + // We successfully create a program + return [program, vertexShader, fragmentShader]; + } + + private loadVertexShader(shaderSource: string): WebGLShader{ + // Create a new vertex shader + return this.loadShader(this.gl.VERTEX_SHADER, shaderSource); + } + + private loadFragmentShader(shaderSource: string): WebGLShader{ + // Create a new fragment shader + return this.loadShader(this.gl.FRAGMENT_SHADER, shaderSource); + } + + private loadShader(type: number, shaderSource: string): WebGLShader{ + const shader = this.gl.createShader(type); + + // If we couldn't create the shader, error + if(shader === null){ + console.warn("Unable to create shader"); + return null; + } + + // Add the source to the shader and compile + this.gl.shaderSource(shader, shaderSource); + this.gl.compileShader(shader); + + // Make sure there were no errors during this process + if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){ + // Not compiled - error + const error = this.gl.getShaderInfoLog(shader); + console.warn("Failed to compile shader: " + error); + + // Clean up + this.gl.deleteShader(shader); + return null; + } + + // Sucess, so return the shader + return shader; + } + + /* ########## GENERAL LOADING FUNCTIONS ########## */ + + private loadTextFile(textFilePath: string, callback: Function): void { + let xobj: XMLHttpRequest = new XMLHttpRequest(); + xobj.overrideMimeType("application/json"); + xobj.open('GET', textFilePath, true); + xobj.onreadystatechange = function () { + if ((xobj.readyState == 4) && (xobj.status == 200)) { + callback(xobj.responseText); + } + }; + xobj.send(null); + } + + /* ########## LOADING BAR INFO ########## */ + + private getLoadPercent(): number { + return (this.loadonly_tilemapsLoaded/this.loadonly_tilemapsToLoad + + this.loadonly_spritesheetsLoaded/this.loadonly_spritesheetsToLoad + + this.loadonly_imagesLoaded/this.loadonly_imagesToLoad + + this.loadonly_audioLoaded/this.loadonly_audioToLoad) + / this.loadonly_typesToLoad; + } + + update(deltaT: number): void { + if(this.loading){ + if(this.onLoadProgress){ + this.onLoadProgress(this.getLoadPercent()); + } + } else if(this.justLoaded){ + this.justLoaded = false; + if(this.onLoadComplete){ + this.onLoadComplete(); + } + } + } +} + +/** + * A class representing a reference to a resource. + * This is used for the exemption list to assure assets and their dependencies don't get + * destroyed if they are still needed. + */ +class ResourceReference { + key: string; + resourceType: ResourceType; + dependencies: Array; + + constructor(key: string, resourceType: ResourceType){ + this.key = key; + this.resourceType = resourceType; + this. dependencies = new Array(); + } + + addDependency(resource: ResourceReference): void { + this.dependencies.push(resource); + } +} + + +enum ResourceType { + IMAGE = "IMAGE", + TILEMAP = "TILEMAP", + SPRITESHEET = "SPRITESHEET", + AUDIO = "AUDIO", + JSON = "JSON", + SHADER = "SHADER" +} + +/** + * A pair representing a key and the path of the resource to load + */ +class KeyPathPair { + key: string; + path: string; + isDependency?: boolean = false; +} + +class KeyPath_Shader { + key: string; + vpath: string; + fpath: string; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Factories/CanvasNodeFactory.ts b/hw3/src/Wolfie2D/Scene/Factories/CanvasNodeFactory.ts new file mode 100644 index 0000000..9611850 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Factories/CanvasNodeFactory.ts @@ -0,0 +1,239 @@ +import Scene from "../Scene"; +import UIElement from "../../Nodes/UIElement"; +import Graphic from "../../Nodes/Graphic"; +import Sprite from "../../Nodes/Sprites/Sprite"; +import AnimatedSprite from "../../Nodes/Sprites/AnimatedSprite"; +import { GraphicType } from "../../Nodes/Graphics/GraphicTypes"; +import { UIElementType } from "../../Nodes/UIElements/UIElementTypes"; +import Point from "../../Nodes/Graphics/Point"; +import Vec2 from "../../DataTypes/Vec2"; +import Button from "../../Nodes/UIElements/Button"; +import Label from "../../Nodes/UIElements/Label"; +import Slider from "../../Nodes/UIElements/Slider"; +import TextInput from "../../Nodes/UIElements/TextInput"; +import Rect from "../../Nodes/Graphics/Rect"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import Line from "../../Nodes/Graphics/Line"; + +// @ignorePage + +/** + * A factory that abstracts adding @reference[CanvasNode]s to the @reference[Scene]. + * Access methods in this factory through Scene.add.[methodName](). + */ +export default class CanvasNodeFactory { + protected scene: Scene; + protected resourceManager: ResourceManager; + + init(scene: Scene): void { + this.scene = scene; + this.resourceManager = ResourceManager.getInstance(); + } + + /** + * Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement + * @param type The type of UIElement to add + * @param layerName The layer to add the UIElement to + * @param options Any additional arguments to feed to the constructor + * @returns A new UIElement + */ + addUIElement = (type: string | UIElementType, layerName: string, options?: Record): UIElement => { + // Get the layer + let layer = this.scene.getLayer(layerName); + + let instance: UIElement; + + switch(type){ + case UIElementType.BUTTON: + instance = this.buildButton(options); + break; + case UIElementType.LABEL: + instance = this.buildLabel(options); + break; + case UIElementType.SLIDER: + instance = this.buildSlider(options); + break; + case UIElementType.TEXT_INPUT: + instance = this.buildTextInput(options); + break; + default: + throw `UIElementType '${type}' does not exist, or is registered incorrectly.` + } + + instance.setScene(this.scene); + instance.id = this.scene.generateId(); + this.scene.getSceneGraph().addNode(instance); + + // Add instance to layer + layer.addNode(instance) + + return instance; + } + + /** + * Adds a sprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new Sprite + */ + addSprite = (key: string, layerName: string): Sprite => { + let layer = this.scene.getLayer(layerName); + + let instance = new Sprite(key); + + // Add instance to scene + instance.setScene(this.scene); + instance.id = this.scene.generateId(); + + if(!(this.scene.isParallaxLayer(layerName) || this.scene.isUILayer(layerName))){ + this.scene.getSceneGraph().addNode(instance); + } + + // Add instance to layer + layer.addNode(instance); + + return instance; + } + + /** + * Adds an AnimatedSprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new AnimatedSprite + */ + addAnimatedSprite = (key: string, layerName: string): AnimatedSprite => { + let layer = this.scene.getLayer(layerName); + let spritesheet = this.resourceManager.getSpritesheet(key); + let instance = new AnimatedSprite(spritesheet); + + // Add instance fo scene + instance.setScene(this.scene); + instance.id = this.scene.generateId(); + + if(!(this.scene.isParallaxLayer(layerName) || this.scene.isUILayer(layerName))){ + this.scene.getSceneGraph().addNode(instance); + } + + // Add instance to layer + layer.addNode(instance); + + return instance; + } + + /** + * Adds a new graphic element to the current Scene + * @param type The type of graphic to add + * @param layerName The layer on which to add the graphic + * @param options Any additional arguments to send to the graphic constructor + * @returns A new Graphic + */ + addGraphic = (type: GraphicType | string, layerName: string, options?: Record): Graphic => { + // Get the layer + let layer = this.scene.getLayer(layerName); + + let instance: Graphic; + + switch(type){ + case GraphicType.POINT: + instance = this.buildPoint(options); + break; + case GraphicType.LINE: + instance = this.buildLine(options); + break; + case GraphicType.RECT: + instance = this.buildRect(options); + break; + default: + throw `GraphicType '${type}' does not exist, or is registered incorrectly.` + } + + // Add instance to scene + instance.setScene(this.scene); + instance.id = this.scene.generateId(); + + if(!(this.scene.isParallaxLayer(layerName) || this.scene.isUILayer(layerName))){ + this.scene.getSceneGraph().addNode(instance); + } + + // Add instance to layer + layer.addNode(instance); + + return instance; + } + + /* ---------- BUILDERS ---------- */ + + buildButton(options?: Record): Button { + this.checkIfPropExists("Button", options, "position", Vec2, "Vec2"); + this.checkIfPropExists("Button", options, "text", "string"); + + return new Button(options.position, options.text); + } + + buildLabel(options?: Record): Label { + this.checkIfPropExists("Label", options, "position", Vec2, "Vec2"); + this.checkIfPropExists("Label", options, "text", "string"); + + return new Label(options.position, options.text) + } + + buildSlider(options: Record): Slider { + this.checkIfPropExists("Slider", options, "position", Vec2, "Vec2"); + + let initValue = 0; + if(options.value !== undefined){ + initValue = options.value; + } + + return new Slider(options.position, initValue); + } + + buildTextInput(options: Record): TextInput { + this.checkIfPropExists("TextInput", options, "position", Vec2, "Vec2"); + + return new TextInput(options.position); + } + + buildPoint(options?: Record): Point { + this.checkIfPropExists("Point", options, "position", Vec2, "Vec2"); + + return new Point(options.position); + } + + buildLine(options?: Record): Point { + this.checkIfPropExists("Line", options, "start", Vec2, "Vec2"); + this.checkIfPropExists("Line", options, "end", Vec2, "Vec2"); + + return new Line(options.start, options.end); + } + + buildRect(options?: Record): Rect { + this.checkIfPropExists("Rect", options, "position", Vec2, "Vec2"); + this.checkIfPropExists("Rect", options, "size", Vec2, "Vec2"); + + return new Rect(options.position, options.size); + } + + /* ---------- ERROR HANDLING ---------- */ + + checkIfPropExists(objectName: string, options: Record, prop: string, type: (new (...args: any) => T) | string, typeName?: string){ + if(!options || options[prop] === undefined){ + // Check that the options object has the property + throw `${objectName} object requires argument ${prop} of type ${typeName}, but none was provided.`; + } else { + // Check that the property has the correct type + if((typeof type) === "string"){ + if(!(typeof options[prop] === type)){ + throw `${objectName} object requires argument ${prop} of type ${type}, but provided ${prop} was not of type ${type}.`; + } + } else if(type instanceof Function){ + // If type is a constructor, check against that + if(!(options[prop] instanceof type)){ + throw `${objectName} object requires argument ${prop} of type ${typeName}, but provided ${prop} was not of type ${typeName}.`; + } + } else { + throw `${objectName} object requires argument ${prop} of type ${typeName}, but provided ${prop} was not of type ${typeName}.`; + } + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Factories/FactoryManager.ts b/hw3/src/Wolfie2D/Scene/Factories/FactoryManager.ts new file mode 100644 index 0000000..455e539 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Factories/FactoryManager.ts @@ -0,0 +1,81 @@ +import Scene from "../Scene"; +import CanvasNodeFactory from "./CanvasNodeFactory"; +import TilemapFactory from "./TilemapFactory"; +import Tilemap from "../../Nodes/Tilemap"; +import { UIElementType } from "../../Nodes/UIElements/UIElementTypes"; +import UIElement from "../../Nodes/UIElement"; +import Sprite from "../../Nodes/Sprites/Sprite"; +import { GraphicType } from "../../Nodes/Graphics/GraphicTypes"; +import Graphic from "../../Nodes/Graphic"; +import AnimatedSprite from "../../Nodes/Sprites/AnimatedSprite"; +import Vec2 from "../../DataTypes/Vec2"; +import Layer from "../Layer"; + +/** + * The manager of all factories used for adding @reference[GameNode]s to the @reference[Scene]. + */ +export default class FactoryManager { + + // Constructors are called here to allow assignment of their functions to functions in this class + private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory(); + private tilemapFactory: TilemapFactory = new TilemapFactory(); + + constructor(scene: Scene, tilemaps: Array){ + this.canvasNodeFactory.init(scene); + this.tilemapFactory.init(scene, tilemaps); + } + + // Expose all of the factories through the factory manager + /** + * Adds an instance of a UIElement to the current scene - i.e. any class that extends UIElement + * @param type The type of UIElement to add + * @param layerName The layer to add the UIElement to + * @param options Any additional arguments to feed to the constructor + * @returns A new UIElement + */ + uiElement(type: string | UIElementType, layerName: string, options?: Record): UIElement { + return this.canvasNodeFactory.addUIElement(type, layerName, options); + } + + /** + * Adds a sprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new Sprite + */ + sprite(key: string, layerName: string): Sprite { + return this.canvasNodeFactory.addSprite(key, layerName); + } + + /** + * Adds an AnimatedSprite to the current scene + * @param key The key of the image the sprite will represent + * @param layerName The layer on which to add the sprite + * @returns A new AnimatedSprite + */ + animatedSprite(key: string, layerName: string): AnimatedSprite { + return this.canvasNodeFactory.addAnimatedSprite(key, layerName); + } + + /** + * Adds a new graphic element to the current Scene + * @param type The type of graphic to add + * @param layerName The layer on which to add the graphic + * @param options Any additional arguments to send to the graphic constructor + * @returns A new Graphic + */ + graphic(type: GraphicType | string, layerName: string, options?: Record): Graphic { + return this.canvasNodeFactory.addGraphic(type, layerName, options); + } + + /** + * Adds a tilemap to the scene + * @param key The key of the loaded tilemap to load + * @param constr The constructor of the desired tilemap + * @param args Additional arguments to send to the tilemap constructor + * @returns An array of Layers, each of which contains a layer of the tilemap as its own Tilemap instance. + */ + tilemap(key: string, scale?: Vec2): Array { + return this.tilemapFactory.add(key, scale); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Factories/TilemapFactory.ts b/hw3/src/Wolfie2D/Scene/Factories/TilemapFactory.ts new file mode 100644 index 0000000..d41f7a1 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Factories/TilemapFactory.ts @@ -0,0 +1,232 @@ +import Scene from "../Scene"; +import Tilemap from "../../Nodes/Tilemap"; +import ResourceManager from "../../ResourceManager/ResourceManager"; +import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap"; +import Layer from "../Layer"; +import Tileset from "../../DataTypes/Tilesets/Tileset"; +import Vec2 from "../../DataTypes/Vec2"; +import { TiledCollectionTile } from "../../DataTypes/Tilesets/TiledData"; +import Sprite from "../../Nodes/Sprites/Sprite"; +import PositionGraph from "../../DataTypes/Graphs/PositionGraph"; +import Navmesh from "../../Pathfinding/Navmesh"; + +// @ignorePage + +/** + * A factory that abstracts adding @reference[Tilemap]s to the @reference[Scene]. + * Access methods in this factory through Scene.add.[methodName](). + */ +export default class TilemapFactory { + private scene: Scene; + private tilemaps: Array; + private resourceManager: ResourceManager; + + init(scene: Scene, tilemaps: Array): void { + this.scene = scene; + this.tilemaps = tilemaps; + this.resourceManager = ResourceManager.getInstance(); + } + + // TODO - This is specifically catered to Tiled tilemaps right now. In the future, + // it would be good to have a "parseTilemap" function that would convert the tilemap + // data into a standard format. This could allow for support from other programs + // or the development of an internal level builder tool + /** + * Adds a tilemap to the scene + * @param key The key of the loaded tilemap to load + * @param constr The constructor of the desired tilemap + * @param args Additional arguments to send to the tilemap constructor + * @returns An array of Layers, each of which contains a layer of the tilemap as its own Tilemap instance. + */ + add = (key: string, scale: Vec2 = new Vec2(1, 1)): Array => { + // Get Tilemap Data + let tilemapData = this.resourceManager.getTilemap(key); + + // Set the constructor for this tilemap to either be orthographic or isometric + let constr: new(...args: any) => Tilemap; + if(tilemapData.orientation === "orthographic"){ + constr = OrthogonalTilemap; + } else { + // No isometric tilemap support right now, so Orthographic tilemap + constr = OrthogonalTilemap; + } + + // Initialize the return value array + let sceneLayers = new Array(); + + // Create all of the tilesets for this tilemap + let tilesets = new Array(); + + let collectionTiles = new Array(); + + for(let tileset of tilemapData.tilesets){ + if(tileset.image){ + // If this is a standard tileset and not a collection, create a tileset for it. + // TODO - We are ignoring collection tilesets for now. This is likely not a great idea in practice, + // as theoretically someone could want to use one for a standard tilemap. We are assuming for now + // that we only want to use them for object layers + tilesets.push(new Tileset(tileset)); + } else { + tileset.tiles.forEach(tile => tile.id += tileset.firstgid); + collectionTiles.push(...tileset.tiles); + } + } + + // Loop over the layers of the tilemap and create tiledlayers or object layers + for(let layer of tilemapData.layers){ + + let sceneLayer; + let isParallaxLayer = false; + let depth = 0; + + if(layer.properties){ + for(let prop of layer.properties){ + if(prop.name === "Parallax"){ + isParallaxLayer = prop.value; + } else if(prop.name === "Depth") { + depth = prop.value; + } + } + } + + if(isParallaxLayer){ + sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1), depth); + } else { + sceneLayer = this.scene.addLayer(layer.name, depth); + } + + if(layer.type === "tilelayer"){ + // Create a new tilemap object for the layer + let tilemap = new constr(tilemapData, layer, tilesets, scale); + tilemap.id = this.scene.generateId(); + tilemap.setScene(this.scene); + + // Add tilemap to scene + this.tilemaps.push(tilemap); + + sceneLayer.addNode(tilemap); + + // Register tilemap with physics if it's collidable + if(tilemap.isCollidable){ + tilemap.addPhysics(); + + if(layer.properties){ + for(let item of layer.properties){ + if(item.name === "Group"){ + tilemap.setGroup(item.value); + } + } + } + } + } else { + + let isNavmeshPoints = false; + let navmeshName; + let edges; + if(layer.properties){ + for(let prop of layer.properties){ + if(prop.name === "NavmeshPoints"){ + isNavmeshPoints = true; + } else if(prop.name === "name"){ + navmeshName = prop.value; + } else if(prop.name === "edges"){ + edges = prop.value + } + } + } + + if(isNavmeshPoints){ + let g = new PositionGraph(); + + for(let obj of layer.objects){ + g.addPositionedNode(new Vec2(obj.x, obj.y)); + } + + for(let edge of edges){ + g.addEdge(edge.from, edge.to); + } + + this.scene.getNavigationManager().addNavigableEntity(navmeshName, new Navmesh(g)); + + continue; + } + + // Layer is an object layer, so add each object as a sprite to a new layer + for(let obj of layer.objects){ + // Check if obj is collidable + let hasPhysics = false; + let isCollidable = false; + let isTrigger = false; + let onEnter = null; + let onExit = null; + let triggerGroup = null; + let group = ""; + + if(obj.properties){ + for(let prop of obj.properties){ + if(prop.name === "HasPhysics"){ + hasPhysics = prop.value; + } else if(prop.name === "Collidable"){ + isCollidable = prop.value; + } else if(prop.name === "Group"){ + group = prop.value; + } else if(prop.name === "IsTrigger"){ + isTrigger = prop.value; + } else if(prop.name === "TriggerGroup"){ + triggerGroup = prop.value; + } else if(prop.name === "TriggerOnEnter"){ + onEnter = prop.value; + } else if(prop.name === "TriggerOnExit"){ + onExit = prop.value; + } + } + } + + let sprite: Sprite; + + // Check if obj is a tile from a tileset + for(let tileset of tilesets){ + if(tileset.hasTile(obj.gid)){ + // The object is a tile from this set + let imageKey = tileset.getImageKey(); + let offset = tileset.getImageOffsetForTile(obj.gid); + sprite = this.scene.add.sprite(imageKey, layer.name); + let size = tileset.getTileSize().clone(); + sprite.position.set((obj.x + size.x/2)*scale.x, (obj.y - size.y/2)*scale.y); + sprite.setImageOffset(offset); + sprite.size.copy(size); + sprite.scale.set(scale.x, scale.y); + } + } + + // Not in a tileset, must correspond to a collection + if(!sprite){ + for(let tile of collectionTiles){ + if(obj.gid === tile.id){ + let imageKey = tile.image; + sprite = this.scene.add.sprite(imageKey, layer.name); + sprite.position.set((obj.x + tile.imagewidth/2)*scale.x, (obj.y - tile.imageheight/2)*scale.y); + sprite.scale.set(scale.x, scale.y); + } + } + } + + // Now we have sprite. Associate it with our physics object if there is one + if(hasPhysics){ + // Make the sprite a static physics object + sprite.addPhysics(sprite.boundary.clone(), Vec2.ZERO, isCollidable, true); + sprite.setGroup(group); + if(isTrigger && triggerGroup !== null){ + sprite.setTrigger(triggerGroup, onEnter, onExit); + } + } + } + } + + // Update the return value + sceneLayers.push(sceneLayer); + } + + return sceneLayers; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Layer.ts b/hw3/src/Wolfie2D/Scene/Layer.ts new file mode 100644 index 0000000..54535ca --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Layer.ts @@ -0,0 +1,184 @@ +import Scene from "./Scene"; +import MathUtils from "../Utils/MathUtils"; +import GameNode from "../Nodes/GameNode"; + + +/** + * A layer in the scene. Layers are used for sorting @reference[GameNode]s by depth. + */ +export default class Layer { + /** The scene this layer belongs to */ + protected scene: Scene; + + /** The name of this layer */ + protected name: string; + + /** Whether this layer is paused or not */ + protected paused: boolean; + + /** Whether this layer is hidden from being rendered or not */ + protected hidden: boolean; + + /** The global alpha level of this layer */ + protected alpha: number; + + /** An array of the GameNodes that belong to this layer */ + protected items: Array; + + /** Whether or not this layer should be ysorted */ + protected ySort: boolean; + + /** The depth of this layer compared to other layers */ + protected depth: number; + + /** + * Creates a new layer. To do this in a game, use the addLayer() method in @refrence[Scene] + * @param scene The scene to add the layer to + * @param name The name of the layer + */ + constructor(scene: Scene, name: string){ + this.scene = scene; + this.name = name; + this.paused = false; + this.hidden = false; + this.alpha = 1; + this.items = new Array(); + this.ySort = false; + this.depth = 0; + } + + /** + * Retreives the name of the layer + * @returns The name of the layer + */ + getName(): string { + return this.name; + } + + /** + * Pauses/Unpauses the layer. Affects all elements in this layer + * @param pauseValue True if the layer should be paused, false if not + */ + setPaused(pauseValue: boolean): void { + this.paused = pauseValue; + } + + /** + * Returns whether or not the layer is paused + */ + isPaused(): boolean { + return this.paused; + } + + /** + * Sets the opacity of the layer + * @param alpha The new opacity value in the range [0, 1] + */ + setAlpha(alpha: number): void { + this.alpha = MathUtils.clamp(alpha, 0, 1); + } + + /** + * Gets the opacity of the layer + * @returns The opacity + */ + getAlpha(): number { + return this.alpha; + } + + /** + * Sets the layer's hidden value. If hidden, a layer will not be rendered, but will still update + * @param hidden The hidden value of the layer + */ + setHidden(hidden: boolean): void { + this.hidden = hidden; + } + + /** + * Returns the hideen value of the lyaer + * @returns True if the scene is hidden, false otherwise + */ + isHidden(): boolean { + return this.hidden; + } + + /** Pauses this scene and hides it */ + disable(): void { + this.paused = true; + this.hidden = true; + } + + /** Unpauses this layer and makes it visible */ + enable(): void { + this.paused = false; + this.hidden = false; + } + + /** + * Sets whether or not the scene will ySort automatically. + * ySorting means that CanvasNodes on this layer will have their depth sorted depending on their y-value. + * This means that if an object is "higher" in the scene, it will sort behind objects that are "lower". + * This is useful for 3/4 view games, or similar situations, where you sometimes want to be in front of objects, + * and other times want to be behind the same objects. + * @param ySort True if ySorting should be active, false if not + */ + setYSort(ySort: boolean): void { + this.ySort = ySort; + } + + /** + * Gets the ySort status of the scene + * @returns True if ySorting is occurring, false otherwise + */ + getYSort(): boolean { + return this.ySort; + } + + /** + * Sets the depth of the layer compared to other layers. A larger number means the layer will be closer to the screen. + * @param depth The depth of the layer. + */ + setDepth(depth: number): void { + this.depth = depth; + } + + /** + * Retrieves the depth of the layer. + * @returns The depth + */ + getDepth(): number { + return this.depth; + } + + /** + * Adds a node to this layer + * @param node The node to add to this layer. + */ + addNode(node: GameNode): void { + this.items.push(node); + node.setLayer(this); + } + + /** + * Removes a node from this layer + * @param node The node to remove + * @returns true if the node was removed, false otherwise + */ + removeNode(node: GameNode): void { + // Find and remove the node + let index = this.items.indexOf(node); + + if(index !== -1){ + this.items.splice(index, 1); + node.setLayer(undefined); + } + } + + /** + * Retreives all GameNodes from this layer + * @returns an Array that contains all of the GameNodes in this layer. + */ + getItems(): Array { + return this.items; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Layers/ParallaxLayer.ts b/hw3/src/Wolfie2D/Scene/Layers/ParallaxLayer.ts new file mode 100644 index 0000000..0260139 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Layers/ParallaxLayer.ts @@ -0,0 +1,23 @@ +import Layer from "../Layer"; +import Vec2 from "../../DataTypes/Vec2"; +import Scene from "../Scene"; + +/** + * An extension of a Layer that has a parallax value. + */ +export default class ParallaxLayer extends Layer { + /** The value of the parallax of the Layer */ + parallax: Vec2; + + /** + * Creates a new ParallaxLayer. + * Use addParallaxLayer() in @reference[Scene] to add a layer of this type to your game. + * @param scene The Scene to add this ParallaxLayer to + * @param name The name of the ParallaxLayer + * @param parallax The parallax level + */ + constructor(scene: Scene, name: string, parallax: Vec2){ + super(scene, name); + this.parallax = parallax; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Layers/UILayer.ts b/hw3/src/Wolfie2D/Scene/Layers/UILayer.ts new file mode 100644 index 0000000..5843129 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Layers/UILayer.ts @@ -0,0 +1,20 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Scene from "../Scene"; +import ParallaxLayer from "./ParallaxLayer"; + +/** + * A Layer strictly to be used for managing UIElements. + * This is intended to be a Layer that always stays in the same place, + * and thus renders things like a HUD or an inventory without taking into consideration the \reference[Viewport] scroll. + */ +export default class UILayer extends ParallaxLayer { + /** + * Creates a new UILayer. + * Use addUILayer() in @reference[Scene] to add a layer of this type to your game. + * @param scene The Scene to add this UILayer to + * @param name The name of the UILayer + */ + constructor(scene: Scene, name: string){ + super(scene, name, Vec2.ZERO); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/Scene.ts b/hw3/src/Wolfie2D/Scene/Scene.ts new file mode 100644 index 0000000..ee7f5a3 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/Scene.ts @@ -0,0 +1,444 @@ +import Layer from "./Layer"; +import Viewport from "../SceneGraph/Viewport"; +import Vec2 from "../DataTypes/Vec2"; +import SceneGraph from "../SceneGraph/SceneGraph"; +import PhysicsManager from "../Physics/PhysicsManager"; +import BasicPhysicsManager from "../Physics/BasicPhysicsManager"; +import SceneGraphArray from "../SceneGraph/SceneGraphArray"; +import FactoryManager from "./Factories/FactoryManager"; +import Tilemap from "../Nodes/Tilemap"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import Game from "../Loop/Game"; +import SceneManager from "./SceneManager"; +import Receiver from "../Events/Receiver"; +import Emitter from "../Events/Emitter"; +import Updateable from "../DataTypes/Interfaces/Updateable"; +import NavigationManager from "../Pathfinding/NavigationManager"; +import AIManager from "../AI/AIManager"; +import Map from "../DataTypes/Map"; +import ParallaxLayer from "./Layers/ParallaxLayer"; +import UILayer from "./Layers/UILayer"; +import CanvasNode from "../Nodes/CanvasNode"; +import GameNode from "../Nodes/GameNode"; +import SceneOptions from "./SceneOptions"; +import RenderingManager from "../Rendering/RenderingManager"; +import Debug from "../Debug/Debug"; +import TimerManager from "../Timing/TimerManager"; +import TweenManager from "../Rendering/Animations/TweenManager"; + +/** + * Scenes are the main container in the game engine. + * Your main scene is the current level or menu of the game, and will contain all of the GameNodes needed. + * Scenes provide an easy way to load assets, add assets to the game world, and unload assets, + * and have lifecycle methods exposed for these functions. + */ +export default class Scene implements Updateable { + /** The size of the game world. */ + protected worldSize: Vec2; + + /** The viewport. */ + protected viewport: Viewport; + + /** A flag that represents whether this scene is running or not. */ + protected running: boolean; + + /** The manager of this scene. */ + protected sceneManager: SceneManager; + + /** The receiver for this scene. */ + protected receiver: Receiver; + + /** The emitter for this scene. */ + protected emitter: Emitter; + + /** This list of tilemaps in this scene. */ + protected tilemaps: Array; + + /** A map from layer names to the layers themselves */ + protected layers: Map; + + /** A map from parallax layer names to the parallax layers themselves */ + protected parallaxLayers: Map; + + /** A map from uiLayer names to the uiLayers themselves */ + protected uiLayers: Map; + + /** The scene graph of the Scene*/ + protected sceneGraph: SceneGraph; + + /** The physics manager of the Scene */ + protected physicsManager: PhysicsManager; + + /** The navigation manager of the Scene */ + protected navManager: NavigationManager; + + /** The AI manager of the Scene */ + protected aiManager: AIManager; + + /** The renderingManager of the scene */ + protected renderingManager: RenderingManager; + + /** An interface that allows the adding of different nodes to the scene */ + public add: FactoryManager; + + /** An interface that allows the loading of different files for use in the scene. An alias for resourceManager */ + public load: ResourceManager; + + /** An interface that allows the loading and unloading of different files for use in the scene */ + public resourceManager: ResourceManager; + + /** The configuration options for this scene */ + public sceneOptions: SceneOptions; + + /** + * Creates a new Scene. To add a new Scene in your game, use changeToScene() in @reference[SceneManager] + * @param viewport The viewport of the game + * @param sceneManager The SceneManager that owns this Scene + * @param renderingManager The RenderingManager that will handle this Scene's rendering + * @param game The instance of the Game + * @param options The options for Scene initialization + */ + constructor(viewport: Viewport, sceneManager: SceneManager, renderingManager: RenderingManager, options: Record){ + this.sceneOptions = SceneOptions.parse(options === undefined ? {} : options); + + this.worldSize = new Vec2(500, 500); + this.viewport = viewport; + this.viewport.setBounds(0, 0, 2560, 1280); + this.running = false; + this.sceneManager = sceneManager; + this.receiver = new Receiver(); + this.emitter = new Emitter(); + + this.tilemaps = new Array(); + this.sceneGraph = new SceneGraphArray(this.viewport, this); + + this.layers = new Map(); + this.uiLayers = new Map(); + this.parallaxLayers = new Map(); + + this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics); + this.navManager = new NavigationManager(); + this.aiManager = new AIManager(); + this.renderingManager = renderingManager; + + this.add = new FactoryManager(this, this.tilemaps); + + this.load = ResourceManager.getInstance() + this.resourceManager = this.load; + + // Get the timer manager and clear any existing timers + TimerManager.getInstance().clearTimers(); + } + + /** A lifecycle method that gets called immediately after a new scene is created, before anything else. */ + initScene(init: Record): void {} + + /** A lifecycle method that gets called when a new scene is created. Load all files you wish to access in the scene here. */ + loadScene(): void {} + + /** A lifecycle method called strictly after loadScene(). Create any game objects you wish to use in the scene here. */ + startScene(): void {} + + /** + * A lifecycle method called every frame of the game. This is where you can dynamically do things like add in new enemies + * @param delta The time this frame represents + */ + updateScene(deltaT: number): void {} + + /** A lifecycle method that gets called on scene destruction. Specify which files you no longer need for garbage collection. */ + unloadScene(): void {} + + update(deltaT: number): void { + this.updateScene(deltaT); + + // Do time updates + TimerManager.getInstance().update(deltaT); + + // Do all AI updates + this.aiManager.update(deltaT); + + // Update all physics objects + this.physicsManager.update(deltaT); + + // Update all canvas objects + this.sceneGraph.update(deltaT); + + // Update all tilemaps + this.tilemaps.forEach(tilemap => { + if(!tilemap.getLayer().isPaused()){ + tilemap.update(deltaT); + } + }); + + // Update all tweens + TweenManager.getInstance().update(deltaT); + + // Update viewport + this.viewport.update(deltaT); + } + + /** + * Collects renderable sets and coordinates with the RenderingManager to draw the Scene + */ + render(): void { + // Get the visible set of nodes + let visibleSet = this.sceneGraph.getVisibleSet(); + + // Add parallax layer items to the visible set (we're rendering them all for now) + this.parallaxLayers.forEach(key => { + let pLayer = this.parallaxLayers.get(key); + for(let node of pLayer.getItems()){ + if(node instanceof CanvasNode){ + visibleSet.push(node); + } + } + }); + + // Send the visible set, tilemaps, and uiLayers to the renderer + this.renderingManager.render(visibleSet, this.tilemaps, this.uiLayers); + + let nodes = this.sceneGraph.getAllNodes(); + this.tilemaps.forEach(tilemap => tilemap.visible ? nodes.push(tilemap) : 0); + Debug.setNodes(nodes); + } + + /** + * Sets the scene as running or not + * @param running True if the Scene should be running, false if not + */ + setRunning(running: boolean): void { + this.running = running; + } + + /** + * Returns whether or not the Scene is running + * @returns True if the scene is running, false otherwise + */ + isRunning(): boolean { + return this.running; + } + + /** + * Removes a node from this Scene + * @param node The node to remove + */ + remove(node: GameNode): void { + // Remove from the scene graph + if(node instanceof CanvasNode){ + this.sceneGraph.removeNode(node); + } + + } + + /** Destroys this scene and all nodes in it */ + destroy(): void { + for(let node of this.sceneGraph.getAllNodes()){ + node.destroy(); + } + + for(let tilemap of this.tilemaps){ + tilemap.destroy(); + } + + this.receiver.destroy(); + + delete this.sceneGraph; + delete this.physicsManager; + delete this.navManager; + delete this.aiManager; + delete this.receiver; + } + + /** + * Adds a new layer to the scene and returns it + * @param name The name of the new layer + * @param depth The depth of the layer + * @returns The newly created Layer + */ + addLayer(name: string, depth?: number): Layer { + if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ + throw `Layer with name ${name} already exists`; + } + + let layer = new Layer(this, name); + + this.layers.add(name, layer); + + if(depth){ + layer.setDepth(depth); + } + + return layer; + } + + /** + * Adds a new parallax layer to this scene and returns it + * @param name The name of the parallax layer + * @param parallax The parallax level + * @param depth The depth of the layer + * @returns The newly created ParallaxLayer + */ + addParallaxLayer(name: string, parallax: Vec2, depth?: number): ParallaxLayer { + if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ + throw `Layer with name ${name} already exists`; + } + + let layer = new ParallaxLayer(this, name, parallax); + + this.parallaxLayers.add(name, layer); + + if(depth){ + layer.setDepth(depth); + } + + return layer; + } + + /** + * Adds a new UILayer to the scene + * @param name The name of the new UIlayer + * @returns The newly created UILayer + */ + addUILayer(name: string): UILayer { + if(this.layers.has(name) || this.parallaxLayers.has(name) || this.uiLayers.has(name)){ + throw `Layer with name ${name} already exists`; + } + + let layer = new UILayer(this, name); + + this.uiLayers.add(name, layer); + + return layer; + } + + /** + * Gets a layer from the scene by name if it exists. + * This can be a Layer or any of its subclasses + * @param name The name of the layer + * @returns The Layer found with that name + */ + getLayer(name: string): Layer { + if(this.layers.has(name)){ + return this.layers.get(name); + } else if(this.parallaxLayers.has(name)){ + return this.parallaxLayers.get(name); + } else if(this.uiLayers.has(name)){ + return this.uiLayers.get(name); + } else { + throw `Requested layer ${name} does not exist.`; + } + } + + /** + * Returns true if this layer is a ParallaxLayer + * @param name The name of the layer + * @returns True if this layer is a ParallaxLayer + */ + isParallaxLayer(name: string): boolean { + return this.parallaxLayers.has(name); + } + + /** + * Returns true if this layer is a UILayer + * @param name The name of the layer + * @returns True if this layer is ParallaxLayer + */ + isUILayer(name: string): boolean { + return this.uiLayers.has(name); + } + + /** + * Returns the translation of this node with respect to camera space (due to the viewport moving). + * This value is affected by the parallax level of the @reference[Layer] the node is on. + * @param node The node to check the viewport with respect to + * @returns A Vec2 containing the translation of viewport with respect to this node. + */ + getViewTranslation(node: GameNode): Vec2 { + let layer = node.getLayer(); + + if(layer instanceof ParallaxLayer || layer instanceof UILayer){ + return this.viewport.getOrigin().mult(layer.parallax); + } else { + return this.viewport.getOrigin(); + } + } + + /** + * Returns the scale level of the view + * @returns The zoom level of the viewport + */ + getViewScale(): number { + return this.viewport.getZoomLevel(); + } + + /** + * Returns the Viewport associated with this scene + * @returns The current Viewport + */ + getViewport(): Viewport { + return this.viewport; + } + + /** + * Gets the world size of this Scene + * @returns The world size in a Vec2 + */ + getWorldSize(): Vec2 { + return this.worldSize; + } + + /** + * Gets the SceneGraph associated with this Scene + * @returns The SceneGraph + */ + getSceneGraph(): SceneGraph { + return this.sceneGraph; + } + + /** + * Gets the PhysicsManager associated with this Scene + * @returns The PhysicsManager + */ + getPhysicsManager(): PhysicsManager { + return this.physicsManager; + } + + /** + * Gets the NavigationManager associated with this Scene + * @returns The NavigationManager + */ + getNavigationManager(): NavigationManager { + return this.navManager; + } + + /** + * Gets the AIManager associated with this Scene + * @returns The AIManager + */ + getAIManager(): AIManager { + return this.aiManager; + } + + /** + * Generates an ID for a GameNode + * @returns The new ID + */ + generateId(): number { + return this.sceneManager.generateId(); + } + + /** + * Retrieves a Tilemap in this Scene + * @param name The name of the Tilemap + * @returns The Tilemap, if one this name exists, otherwise null + */ + getTilemap(name: string): Tilemap { + for(let tilemap of this .tilemaps){ + if(tilemap.name === name){ + return tilemap; + } + } + + return null; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/SceneManager.ts b/hw3/src/Wolfie2D/Scene/SceneManager.ts new file mode 100644 index 0000000..0809a30 --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/SceneManager.ts @@ -0,0 +1,125 @@ +import Scene from "./Scene"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import Viewport from "../SceneGraph/Viewport"; +import RenderingManager from "../Rendering/RenderingManager"; +import MemoryUtils from "../Utils/MemoryUtils"; + +/** + * The SceneManager acts as an interface to create Scenes, and handles the lifecycle methods of Scenes. + * It gives Scenes access to information they need from the @reference[Game] class while keeping a layer of separation. + */ +export default class SceneManager { + /** The current Scene of the game */ + protected currentScene: Scene; + + /** The Viewport of the game */ + protected viewport: Viewport; + + /** A reference to the ResourceManager */ + protected resourceManager: ResourceManager; + + /** A counter to keep track of game ids */ + protected idCounter: number; + + /** The RenderingManager of the game */ + protected renderingManager: RenderingManager; + + /** For consistency, only change scenes at the beginning of the update cycle */ + protected pendingScene: Scene; + protected pendingSceneInit: Record; + + /** + * Creates a new SceneManager + * @param viewport The Viewport of the game + * @param game The Game instance + * @param renderingManager The RenderingManager of the game + */ + constructor(viewport: Viewport, renderingManager: RenderingManager){ + this.resourceManager = ResourceManager.getInstance(); + this.viewport = viewport; + this.renderingManager = renderingManager; + this.idCounter = 0; + this.pendingScene = null; + } + + /** + * Add a scene as the main scene. + * Use this method if you've created a subclass of Scene, and you want to add it as the main Scene. + * @param constr The constructor of the scene to add + * @param init An object to pass to the init function of the new scene + */ + public changeToScene(constr: new (...args: any) => T, init?: Record, options?: Record): void { + console.log("Creating the new scene - change is pending until next update"); + this.pendingScene = new constr(this.viewport, this, this.renderingManager, options); + this.pendingSceneInit = init; + } + + protected doSceneChange(){ + console.log("Performing scene change"); + this.viewport.setCenter(this.viewport.getHalfSize().x, this.viewport.getHalfSize().y); + + if(this.currentScene){ + console.log("Unloading old scene") + this.currentScene.unloadScene(); + + console.log("Destroying old scene"); + this.currentScene.destroy(); + } + + console.log("Unloading old resources..."); + this.resourceManager.unloadAllResources(); + + // Make the pending scene the current one + this.currentScene = this.pendingScene; + + // Make the pending scene null + this.pendingScene = null; + + // Init the scene + this.currentScene.initScene(this.pendingSceneInit); + + // Enqueue all scene asset loads + this.currentScene.loadScene(); + + // Load all assets + console.log("Starting Scene Load"); + this.resourceManager.loadResourcesFromQueue(() => { + console.log("Starting Scene"); + this.currentScene.startScene(); + this.currentScene.setRunning(true); + }); + + this.renderingManager.setScene(this.currentScene); + } + + /** + * Generates a unique ID + * @returns A new ID + */ + public generateId(): number { + return this.idCounter++; + } + + /** + * Renders the current Scene + */ + public render(): void { + if(this.currentScene){ + this.currentScene.render(); + } + } + + /** + * Updates the current Scene + * @param deltaT The timestep of the Scene + */ + public update(deltaT: number){ + if(this.pendingScene !== null){ + this.doSceneChange(); + } + + if(this.currentScene && this.currentScene.isRunning()){ + this.currentScene.update(deltaT); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Scene/SceneOptions.ts b/hw3/src/Wolfie2D/Scene/SceneOptions.ts new file mode 100644 index 0000000..e52f6be --- /dev/null +++ b/hw3/src/Wolfie2D/Scene/SceneOptions.ts @@ -0,0 +1,25 @@ +import ArrayUtils from "../Utils/ArrayUtils"; + +// @ignorePage + +/** + * The options to give a @reference[Scene] for initialization + */ +export default class SceneOptions { + physics: { + groups: Array, + collisions: Array>; + } + + static parse(options: Record): SceneOptions{ + let sOpt = new SceneOptions(); + + if(options.physics === undefined){ + sOpt.physics = {groups: undefined, collisions: undefined}; + } else { + sOpt.physics = options.physics; + } + + return sOpt; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/SceneGraph/SceneGraph.ts b/hw3/src/Wolfie2D/SceneGraph/SceneGraph.ts new file mode 100644 index 0000000..cdde06f --- /dev/null +++ b/hw3/src/Wolfie2D/SceneGraph/SceneGraph.ts @@ -0,0 +1,131 @@ +import Viewport from "./Viewport"; +import CanvasNode from "../Nodes/CanvasNode"; +import Map from "../DataTypes/Map"; +import Vec2 from "../DataTypes/Vec2"; +import Scene from "../Scene/Scene"; +import AABB from "../DataTypes/Shapes/AABB"; + +/** + * An abstract interface of a SceneGraph. + * Exposes methods for use by other code, but leaves the implementation up to the subclasses. + * The SceneGraph manages the positions of all GameNodes, and can easily prune a visible set for rendering. + */ +export default abstract class SceneGraph { + /** A reference to the viewport */ + protected viewport: Viewport; + /** A map of CanvasNodes in this SceneGraph */ + protected nodeMap: Array; + /** A counter of IDs for nodes in this SceneGraph */ + protected idCounter: number; + /** A reference to the Scene this SceneGraph belongs to */ + protected scene: Scene; + + /** + * Creates a new SceneGraph + * @param viewport The viewport + * @param scene The Scene this SceneGraph belongs to + */ + constructor(viewport: Viewport, scene: Scene){ + this.viewport = viewport; + this.scene = scene; + this.nodeMap = new Array(); + this.idCounter = 0; + } + + /** + * Add a node to the SceneGraph + * @param node The CanvasNode to add to the SceneGraph + * @returns The SceneGraph ID of this newly added CanvasNode + */ + addNode(node: CanvasNode): number { + this.nodeMap[node.id] = node; + this.addNodeSpecific(node, this.idCounter); + this.idCounter += 1; + return this.idCounter - 1; + }; + + /** + * An overridable method to add a CanvasNode to the specific data structure of the SceneGraph + * @param node The node to add to the data structure + * @param id The id of the CanvasNode + */ + protected abstract addNodeSpecific(node: CanvasNode, id: number): void; + + /** + * Removes a node from the SceneGraph + * @param node The node to remove + */ + removeNode(node: CanvasNode): void { + // Find and remove node in O(n) + this.nodeMap[node.id] = undefined; + this.removeNodeSpecific(node, node.id); + }; + + /** + * The specific implementation of removing a node + * @param node The node to remove + * @param id The id of the node to remove + */ + protected abstract removeNodeSpecific(node: CanvasNode, id: number): void; + + /** + * Get a specific node using its id + * @param id The id of the CanvasNode to retrieve + * @returns The node with this ID + */ + getNode(id: number): CanvasNode { + return this.nodeMap[id]; + } + + /** + * Returns the nodes at specific coordinates + * @param vecOrX The x-coordinate of the position, or the coordinates in a Vec2 + * @param y The y-coordinate of the position + * @returns An array of nodes found at the position provided + */ + getNodesAt(vecOrX: Vec2 | number, y: number = null): Array { + if(vecOrX instanceof Vec2){ + return this.getNodesAtCoords(vecOrX.x, vecOrX.y); + } else { + return this.getNodesAtCoords(vecOrX, y); + } + } + + /** + * Returns the nodes that overlap a specific boundary + * @param boundary The region to check + * @returns An array of nodes found overlapping the provided boundary + */ + abstract getNodesInRegion(boundary: AABB): Array; + + /** + * Returns all nodes in the SceneGraph + * @returns An Array containing all nodes in the SceneGraph + */ + getAllNodes(): Array { + let arr = new Array(); + for(let i = 0; i < this.nodeMap.length; i++){ + if(this.nodeMap[i] !== undefined){ + arr.push(this.nodeMap[i]); + } + } + return arr; + } + + /** + * The specific implementation of getting a node at certain coordinates + * @param x The x-coordinates of the node + * @param y The y-coordinates of the node + */ + protected abstract getNodesAtCoords(x: number, y: number): Array; + + abstract update(deltaT: number): void; + + abstract render(ctx: CanvasRenderingContext2D): void; + + /** + * Gets the visible set of CanvasNodes based on the @reference[Viewport] + * @returns An array containing all visible nodes in the SceneGraph + */ + abstract getVisibleSet(): Array; +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/SceneGraph/SceneGraphArray.ts b/hw3/src/Wolfie2D/SceneGraph/SceneGraphArray.ts new file mode 100644 index 0000000..d07ced4 --- /dev/null +++ b/hw3/src/Wolfie2D/SceneGraph/SceneGraphArray.ts @@ -0,0 +1,93 @@ +import SceneGraph from "./SceneGraph"; +import CanvasNode from "../Nodes/CanvasNode"; +import Viewport from "./Viewport"; +import Scene from "../Scene/Scene"; +import AABB from "../DataTypes/Shapes/AABB"; +import Stats from "../Debug/Stats"; + +/** + * An implementation of a SceneGraph that simply stored CanvasNodes in an array. + */ +export default class SceneGraphArray extends SceneGraph { + /** The list of CanvasNodes in this SceneGraph */ + private nodeList: Array; + + /** + * Creates a new SceneGraphArray + * @param viewport The Viewport + * @param scene The Scene this SceneGraph belongs to + */ + constructor(viewport: Viewport, scene: Scene){ + super(viewport, scene); + + this.nodeList = new Array(); + } + + // @override + protected addNodeSpecific(node: CanvasNode, id: number): void { + this.nodeList.push(node); + } + + // @override + protected removeNodeSpecific(node: CanvasNode, id: number): void { + let index = this.nodeList.indexOf(node); + if(index > -1){ + this.nodeList.splice(index, 1); + } + } + + // @override + getNodesAtCoords(x: number, y: number): Array { + let results = []; + + for(let node of this.nodeList){ + if(node.contains(x, y)){ + results.push(node); + } + } + + return results; + } + + // @override + getNodesInRegion(boundary: AABB): Array { + let t0 = performance.now(); + let results = []; + + for(let node of this.nodeList){ + if(boundary.overlaps(node.boundary)){ + results.push(node); + } + } + let t1 = performance.now(); + Stats.log("sgquery", (t1-t0)); + + return results; + } + + update(deltaT: number): void { + let t0 = performance.now(); + for(let node of this.nodeList){ + if(!node.getLayer().isPaused()){ + node.update(deltaT); + } + } + let t1 = performance.now(); + Stats.log("sgupdate", (t1-t0)); + } + + render(ctx: CanvasRenderingContext2D): void {} + + // @override + getVisibleSet(): Array { + let visibleSet = new Array(); + + for(let node of this.nodeList){ + if(!node.getLayer().isHidden() && node.visible && this.viewport.includes(node)){ + visibleSet.push(node); + } + } + + return visibleSet; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/SceneGraph/SceneGraphQuadTree.ts b/hw3/src/Wolfie2D/SceneGraph/SceneGraphQuadTree.ts new file mode 100644 index 0000000..47dd0f7 --- /dev/null +++ b/hw3/src/Wolfie2D/SceneGraph/SceneGraphQuadTree.ts @@ -0,0 +1,98 @@ +import SceneGraph from "./SceneGraph"; +import CanvasNode from "../Nodes/CanvasNode"; +import Viewport from "./Viewport"; +import Scene from "../Scene/Scene"; +import RegionQuadTree from "../DataTypes/RegionQuadTree"; +import Vec2 from "../DataTypes/Vec2"; +import AABB from "../DataTypes/Shapes/AABB"; +import Stats from "../Debug/Stats"; + +/** + * An implementation of a SceneGraph that uses a @reference[RegionQuadTree] to store @reference[CanvasNode]s. + */ +export default class SceneGraphQuadTree extends SceneGraph { + /** The QuadTree used to store the CanvasNodes */ + private qt: RegionQuadTree; + + /** A list of nodes to help out the QuadTree */ + private nodes: Array; + + /** + * Creates a new SceneGraphQuadTree + * @param viewport The Viewport + * @param scene The Scene this SceneGraph belongs to + */ + constructor(viewport: Viewport, scene: Scene){ + super(viewport, scene); + + let size = this.scene.getWorldSize(); + this.qt = new RegionQuadTree(size.clone().scale(1/2), size.clone().scale(1/2), 5, 30); + this.nodes = new Array(); + } + + // @override + protected addNodeSpecific(node: CanvasNode, id: number): void { + this.nodes.push(node); + } + + // @override + protected removeNodeSpecific(node: CanvasNode, id: number): void { + let index = this.nodes.indexOf(node); + if(index >= 0){ + this.nodes.splice(index, 1); + } + } + + // @override + getNodesAtCoords(x: number, y: number): Array { + return this.qt.queryPoint(new Vec2(x, y)); + } + + // @override + getNodesInRegion(boundary: AABB): Array { + let t0 = performance.now(); + let res = this.qt.queryRegion(boundary); + let t1 = performance.now(); + + Stats.log("sgquery", (t1-t0)); + + return res; + } + + update(deltaT: number): void { + let t0 = performance.now(); + this.qt.clear(); + let t1 = performance.now(); + + Stats.log("sgclear", (t1-t0)); + + t0 = performance.now(); + for(let node of this.nodes){ + this.qt.insert(node); + } + t1 = performance.now(); + + Stats.log("sgfill", (t1-t0)); + + t0 = performance.now(); + this.nodes.forEach((node: CanvasNode) => node.update(deltaT)); + t1 = performance.now(); + + Stats.log("sgupdate", (t1-t0)); + } + + render(ctx: CanvasRenderingContext2D): void { + let origin = this.viewport.getOrigin(); + let zoom = this.viewport.getZoomLevel(); + this.qt.render_demo(ctx, origin, zoom); + } + + // @override + getVisibleSet(): Array { + let visibleSet = this.qt.queryRegion(this.viewport.getView()); + + visibleSet = visibleSet.filter(node => !node.getLayer().isHidden()); + + return visibleSet; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/SceneGraph/Viewport.ts b/hw3/src/Wolfie2D/SceneGraph/Viewport.ts new file mode 100644 index 0000000..d2eceba --- /dev/null +++ b/hw3/src/Wolfie2D/SceneGraph/Viewport.ts @@ -0,0 +1,286 @@ +import Vec2 from "../DataTypes/Vec2"; +import GameNode from "../Nodes/GameNode"; +import CanvasNode from "../Nodes/CanvasNode"; +import MathUtils from "../Utils/MathUtils"; +import Queue from "../DataTypes/Queue"; +import AABB from "../DataTypes/Shapes/AABB"; +import Input from "../Input/Input"; +import ParallaxLayer from "../Scene/Layers/ParallaxLayer"; +import UILayer from "../Scene/Layers/UILayer"; + +/** + * The viewport of the game. Corresponds to the visible window displayed in the browser. + * The viewport keeps track of its position in the game world, and can act as a camera to follow objects. + */ +export default class Viewport { + /** The AABB that contains the position and size of the viewport view */ + private view: AABB; + /** The boundary for the viewport. This represents the limits to where the viewport can go */ + private boundary: AABB; + /** The GameNode the Viewport is following */ + private following: GameNode; + /** The position the GameNode is focusing on. This is overridden if "following" is set. */ + private focus: Vec2; + + /** A queue of previous positions of what this viewport is following. Used for smoothing viewport movement */ + private lastPositions: Queue; + + /** The number of previous positions this viewport tracks */ + private smoothingFactor: number; + + /** A boolean tha represents whether the player can zoom by scrolling with the mouse wheel */ + private scrollZoomEnabled: boolean; + + /** The amount that is zoomed in or out. */ + private ZOOM_FACTOR: number = 1.2; + + /** The size of the canvas */ + private canvasSize: Vec2; + + constructor(canvasSize: Vec2, zoomLevel: number){ + this.view = new AABB(Vec2.ZERO, Vec2.ZERO); + this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO); + this.lastPositions = new Queue(); + this.smoothingFactor = 10; + this.scrollZoomEnabled = false; + this.canvasSize = Vec2.ZERO; + this.focus = Vec2.ZERO; + + // Set the size of the canvas + this.setCanvasSize(canvasSize); + + // Set the size of the viewport + this.setSize(canvasSize); + this.setZoomLevel(zoomLevel); + + // Set the center (and make the viewport stay there) + this.setCenter(this.view.halfSize.clone()); + this.setFocus(this.view.halfSize.clone()); + } + + /** Enables the viewport to zoom in and out */ + enableZoom(): void { + this.scrollZoomEnabled = true; + } + + /** + * Returns the position of the viewport + * @returns The center of the viewport as a Vec2 + */ + getCenter(): Vec2 { + return this.view.center; + } + + /** + * Returns a new Vec2 with the origin of the viewport + * @returns The top left cornder of the Vieport as a Vec2 + */ + getOrigin(): Vec2 { + return new Vec2(this.view.left, this.view.top); + } + + /** + * Returns the region visible to this viewport + * @returns The AABB containing the region visible to the viewport + */ + getView(): AABB { + return this.view; + } + + /** + * Set the position of the viewport + * @param vecOrX The new position or the x-coordinate of the new position + * @param y The y-coordinate of the new position + */ + setCenter(vecOrX: Vec2 | number, y: number = null): void { + let pos: Vec2; + if(vecOrX instanceof Vec2){ + pos = vecOrX; + } else { + pos = new Vec2(vecOrX, y); + } + + this.view.center = pos; + } + + /** + * Returns the size of the viewport as a Vec2 + * @returns The half-size of the viewport as a Vec2 + */ + getHalfSize(): Vec2 { + return this.view.getHalfSize(); + } + + /** + * Sets the size of the viewport + * @param vecOrX The new width of the viewport or the new size as a Vec2 + * @param y The new height of the viewport + */ + setSize(vecOrX: Vec2 | number, y: number = null): void { + if(vecOrX instanceof Vec2){ + this.view.setHalfSize(vecOrX.scaled(1/2)); + } else { + this.view.setHalfSize(new Vec2(vecOrX/2, y/2)); + } + } + + /** + * Sets the half-size of the viewport + * @param vecOrX The new half-width of the viewport or the new half-size as a Vec2 + * @param y The new height of the viewport + */ + setHalfSize(vecOrX: Vec2 | number, y: number = null): void { + if(vecOrX instanceof Vec2){ + this.view.setHalfSize(vecOrX.clone()); + } else { + this.view.setHalfSize(new Vec2(vecOrX, y)); + } + } + + /** + * Updates the viewport with the size of the current Canvas + * @param vecOrX The width of the canvas, or the canvas size as a Vec2 + * @param y The height of the canvas + */ + setCanvasSize(vecOrX: Vec2 | number, y: number = null): void { + if(vecOrX instanceof Vec2){ + this.canvasSize = vecOrX.clone(); + } else { + this.canvasSize = new Vec2(vecOrX, y); + } + } + + /** + * Sets the zoom level of the viewport + * @param zoom The zoom level + */ + setZoomLevel(zoom: number): void { + this.view.halfSize.copy(this.canvasSize.scaled(1/zoom/2)); + } + + /** + * Gets the zoom level of the viewport + * @returns The zoom level + */ + getZoomLevel(): number { + return this.canvasSize.x/this.view.hw/2 + } + + /** + * Sets the smoothing factor for the viewport movement. + * @param smoothingFactor The smoothing factor for the viewport + */ + setSmoothingFactor(smoothingFactor: number): void { + if(smoothingFactor < 1) smoothingFactor = 1; + this.smoothingFactor = smoothingFactor; + } + + /** + * Tells the viewport to focus on a point. Overidden by "following". + * @param focus The point the viewport should focus on + */ + setFocus(focus: Vec2): void { + this.focus.copy(focus); + } + + /** + * Returns true if the CanvasNode is inside of the viewport + * @param node The node to check + * @returns True if the node is currently visible in the viewport, false if not + */ + includes(node: CanvasNode): boolean { + let parallax = node.getLayer() instanceof ParallaxLayer || node.getLayer() instanceof UILayer ? (node.getLayer()).parallax : new Vec2(1, 1); + let center = this.view.center.clone(); + this.view.center.mult(parallax); + let overlaps = this.view.overlaps(node.boundary); + this.view.center = center + return overlaps; + } + + // TODO: Put some error handling on this for trying to make the bounds too small for the viewport + // TODO: This should probably be done automatically, or should consider the aspect ratio or something + /** + * Sets the bounds of the viewport + * @param lowerX The left edge of the viewport + * @param lowerY The top edge of the viewport + * @param upperX The right edge of the viewport + * @param upperY The bottom edge of the viewport + */ + setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void { + let hwidth = (upperX - lowerX)/2; + let hheight = (upperY - lowerY)/2; + let x = lowerX + hwidth; + let y = lowerY + hheight; + this.boundary.center.set(x, y); + this.boundary.halfSize.set(hwidth, hheight); + } + + /** + * Make the viewport follow the specified GameNode + * @param node The GameNode to follow + */ + follow(node: GameNode): void { + this.following = node; + } + + updateView(): void { + if(this.lastPositions.getSize() > this.smoothingFactor){ + this.lastPositions.dequeue(); + } + + // Get the average of the last 10 positions + let pos = Vec2.ZERO; + this.lastPositions.forEach(position => pos.add(position)); + pos.scale(1/this.lastPositions.getSize()); + + // Set this position either to the object or to its bounds + pos.x = MathUtils.clamp(pos.x, this.boundary.left + this.view.hw, this.boundary.right - this.view.hw); + pos.y = MathUtils.clamp(pos.y, this.boundary.top + this.view.hh, this.boundary.bottom - this.view.hh); + + // Assure there are no lines in the tilemap + pos.x = Math.floor(pos.x); + pos.y = Math.floor(pos.y); + + this.view.center.copy(pos); + } + + update(deltaT: number): void { + // If zoom is enabled + if(this.scrollZoomEnabled){ + if(Input.didJustScroll()){ + let currentSize = this.view.getHalfSize().clone(); + if(Input.getScrollDirection() < 0){ + // Zoom in + currentSize.scale(1/this.ZOOM_FACTOR); + } else { + // Zoom out + currentSize.scale(this.ZOOM_FACTOR); + } + + if(currentSize.x > this.boundary.hw){ + let factor = this.boundary.hw/currentSize.x; + currentSize.x = this.boundary.hw; + currentSize.y *= factor; + } + + if(currentSize.y > this.boundary.hh){ + let factor = this.boundary.hh/currentSize.y; + currentSize.y = this.boundary.hh; + currentSize.x *= factor; + } + + this.view.setHalfSize(currentSize); + } + } + + // If viewport is following an object + if(this.following){ + // Update our list of previous positions + this.lastPositions.enqueue(this.following.position.clone()); + } else { + this.lastPositions.enqueue(this.focus); + } + + this.updateView(); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Sound/AudioManager.ts b/hw3/src/Wolfie2D/Sound/AudioManager.ts new file mode 100644 index 0000000..0e438d2 --- /dev/null +++ b/hw3/src/Wolfie2D/Sound/AudioManager.ts @@ -0,0 +1,244 @@ +import Map from "../DataTypes/Map"; +import Receiver from "../Events/Receiver"; +import ResourceManager from "../ResourceManager/ResourceManager"; +import { GameEventType } from "../Events/GameEventType"; + +/** + * Manages any sounds or music needed for the game. + * Through the EventQueue, exposes interface to play sounds so GameNodes can activate sounds without + * needing direct references to the audio system + */ +export default class AudioManager { + private static instance: AudioManager; + + /** The event receiver of this AudioManager */ + private receiver: Receiver; + + /** A Map of the names of currently playing (or paused) sounds to their AudioBuffers */ + private currentSounds: Map; + + private audioCtx: AudioContext; + + private gainNodes: Array; + + private constructor(){ + this.initAudio(); + this.receiver = new Receiver(); + this.receiver.subscribe([ + GameEventType.PLAY_SOUND, + GameEventType.STOP_SOUND, + GameEventType.PLAY_MUSIC, + GameEventType.PLAY_SFX, + GameEventType.MUTE_CHANNEL, + GameEventType.UNMUTE_CHANNEL + ]); + this.currentSounds = new Map(); + + this.gainNodes = new Array(MAX_AUDIO_CHANNELS); + this.initGainNodes(); + } + + /** + * Get the instance of the AudioManager class or create a new one if none exists + * @returns The AudioManager + */ + public static getInstance(): AudioManager { + if(!this.instance){ + this.instance = new AudioManager(); + } + return this.instance; + } + + /** + * Initializes the webAudio context + */ + private initAudio(): void { + try { + window.AudioContext = window.AudioContext;// || window.webkitAudioContext; + this.audioCtx = new AudioContext(); + console.log('Web Audio API successfully loaded'); + } catch(e) { + console.warn('Web Audio API is not supported in this browser'); + } + } + + private initGainNodes(): void { + for(let i = 0; i < MAX_AUDIO_CHANNELS; i++){ + this.gainNodes[i] = this.audioCtx.createGain(); + } + } + + /** + * Returns the current audio context + * @returns The AudioContext + */ + public getAudioContext(): AudioContext { + return this.audioCtx; + } + + /* + According to the MDN, create a new sound for every call: + + An AudioBufferSourceNode can only be played once; after each call to start(), you have to create a new node + if you want to play the same sound again. Fortunately, these nodes are very inexpensive to create, and the + actual AudioBuffers can be reused for multiple plays of the sound. Indeed, you can use these nodes in a + "fire and forget" manner: create the node, call start() to begin playing the sound, and don't even bother to + hold a reference to it. It will automatically be garbage-collected at an appropriate time, which won't be + until sometime after the sound has finished playing. + */ + /** + * Creates a new sound from the key of a loaded audio file + * @param key The key of the loaded audio file to create a new sound for + * @returns The newly created AudioBuffer + */ + protected createSound(key: string, holdReference: boolean, channel: AudioChannelType, options: Map): AudioBufferSourceNode { + // Get audio buffer + let buffer = ResourceManager.getInstance().getAudio(key); + + // Create a sound source + var source = this.audioCtx.createBufferSource(); + + // Tell the source which sound to play + source.buffer = buffer; + + // Add any additional nodes + const nodes: Array = [source]; + + // Do any additional nodes here? + // Of course, there aren't any supported yet... + + // Add the gain node for this channel + nodes.push(this.gainNodes[channel]); + + // Connect any nodes along the path + for(let i = 1; i < nodes.length; i++){ + nodes[i-1].connect(nodes[i]); + } + + // Connect the source to the context's destination + nodes[nodes.length - 1].connect(this.audioCtx.destination); + + return source; + } + + /** + * Play the sound specified by the key + * @param key The key of the sound to play + * @param loop A boolean for whether or not to loop the sound + * @param holdReference A boolean for whether or not we want to hold on to a reference of the audio node. This is good for playing music on a loop that will eventually need to be stopped. + */ + protected playSound(key: string, loop: boolean, holdReference: boolean, channel: AudioChannelType, options: Map): void { + let sound = this.createSound(key, holdReference, channel, options); + + if(loop){ + sound.loop = true; + } + + // Add a reference of the new sound to a map. This will allow us to stop a looping or long sound at a later time + if(holdReference){ + this.currentSounds.add(key, sound); + } + + sound.start(); + } + + /** + * Stop the sound specified by the key + */ + protected stopSound(key: string): void { + let sound = this.currentSounds.get(key); + if(sound){ + sound.stop(); + this.currentSounds.delete(key); + } + } + + protected muteChannel(channel: AudioChannelType){ + this.gainNodes[channel].gain.setValueAtTime(0, this.audioCtx.currentTime); + } + + protected unmuteChannel(channel: AudioChannelType){ + this.gainNodes[channel].gain.setValueAtTime(1, this.audioCtx.currentTime); + } + + /** + * Sets the volume of a channel using the GainNode for that channel. For more + * information on GainNodes, see https://developer.mozilla.org/en-US/docs/Web/API/GainNode + * @param channel The audio channel to set the volume for + * @param volume The volume of the channel. 0 is muted. Values below zero will be set to zero. + */ + static setVolume(channel: AudioChannelType, volume: number){ + if(volume < 0){ + volume = 0; + } + + const am = AudioManager.getInstance(); + am.gainNodes[channel].gain.setValueAtTime(volume, am.audioCtx.currentTime); + } + + /** + * Returns the GainNode for this channel. + * Learn more about GainNodes here https://developer.mozilla.org/en-US/docs/Web/API/GainNode + * DON'T USE THIS UNLESS YOU KNOW WHAT YOU'RE DOING + * @param channel The channel + * @returns The GainNode for the specified channel + */ + getChannelGainNode(channel: AudioChannelType){ + return this.gainNodes[channel]; + } + + update(deltaT: number): void { + // Play each audio clip requested + // TODO - Add logic to merge sounds if there are multiple of the same key + while(this.receiver.hasNextEvent()){ + let event = this.receiver.getNextEvent(); + if(event.type === GameEventType.PLAY_SOUND || event.type === GameEventType.PLAY_MUSIC || event.type === GameEventType.PLAY_SFX){ + let soundKey = event.data.get("key"); + let loop = event.data.get("loop"); + let holdReference = event.data.get("holdReference"); + + let channel = AudioChannelType.DEFAULT; + + if(event.type === GameEventType.PLAY_MUSIC){ + channel = AudioChannelType.MUSIC; + } else if(GameEventType.PLAY_SFX){ + channel = AudioChannelType.SFX; + } else if(event.data.has("channel")){ + channel = event.data.get("channel"); + } + + this.playSound(soundKey, loop, holdReference, channel, event.data); + } + + if(event.type === GameEventType.STOP_SOUND){ + let soundKey = event.data.get("key"); + this.stopSound(soundKey); + } + + if(event.type === GameEventType.MUTE_CHANNEL){ + this.muteChannel(event.data.get("channel")); + } + + if(event.type === GameEventType.UNMUTE_CHANNEL){ + this.unmuteChannel(event.data.get("channel")); + } + } + } +} + +export enum AudioChannelType { + DEFAULT = 0, + SFX = 1, + MUSIC = 2, + CUSTOM_1 = 3, + CUSTOM_2 = 4, + CUSTOM_3 = 5, + CUSTOM_4 = 6, + CUSTOM_5 = 7, + CUSTOM_6 = 8, + CUSTOM_7 = 9, + CUSTOM_8 = 10, + CUSTOM_9 = 11, +} + +export const MAX_AUDIO_CHANNELS = 12; \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Timing/Timer.ts b/hw3/src/Wolfie2D/Timing/Timer.ts new file mode 100644 index 0000000..d22d235 --- /dev/null +++ b/hw3/src/Wolfie2D/Timing/Timer.ts @@ -0,0 +1,108 @@ +import Updateable from "../DataTypes/Interfaces/Updateable"; +import MathUtils from "../Utils/MathUtils"; +import TimerManager from "./TimerManager"; + +export default class Timer implements Updateable { + + /** The current state of this timer */ + protected state: TimerState; + + /** The function to call when this timer ends */ + protected onEnd: Function; + + /** Whether or not this timer should loop */ + protected loop: boolean; + + /** The total amount of time this timer runs for */ + protected totalTime: number; + + /** The amount of time left on the current run */ + protected timeLeft: number; + + /** The number of times this timer has been run */ + protected numRuns: number; + + constructor(time: number, onEnd?: Function, loop: boolean = false){ + // Register this timer + TimerManager.getInstance().addTimer(this); + + this.totalTime = time; + this.timeLeft = 0; + this.onEnd = onEnd; + this.loop = loop; + this.state = TimerState.STOPPED; + this.numRuns = 0; + } + + isStopped(){ + return this.state === TimerState.STOPPED; + } + + isPaused(){ + return this.state === TimerState.PAUSED; + } + + /** + * Returns whether or not this timer has been run before + * @returns true if it has been run at least once (after the latest reset), and false otherwise + */ + hasRun(): boolean { + return this.numRuns > 0; + } + + start(time?: number){ + if(time !== undefined){ + this.totalTime = time; + } + this.state = TimerState.ACTIVE; + this.timeLeft = this.totalTime; + } + + /** Resets this timer. Sets the progress back to zero, and sets the number of runs back to zero */ + reset(){ + this.timeLeft = this.totalTime; + this.numRuns = 0; + } + + pause(): void { + this.state = TimerState.PAUSED; + } + + update(deltaT: number){ + if(this.state === TimerState.ACTIVE){ + this.timeLeft -= deltaT*1000; + + if(this.timeLeft <= 0){ + this.timeLeft = MathUtils.clampLow0(this.timeLeft); + this.end(); + } + } + } + + protected end(){ + // Update the state + this.state = TimerState.STOPPED; + this.numRuns += 1; + + // Call the end function if there is one + if(this.onEnd){ + this.onEnd(); + } + + // Loop if we want to + if(this.loop){ + this.state = TimerState.ACTIVE; + this.timeLeft = this.totalTime; + } + } + + toString(): string{ + return "Timer: " + this.state + " - Time Left: " + this.timeLeft + "ms of " + this.totalTime + "ms"; + } +} + +export enum TimerState { + ACTIVE = "ACTIVE", + PAUSED = "PAUSED", + STOPPED = "STOPPED" +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Timing/TimerManager.ts b/hw3/src/Wolfie2D/Timing/TimerManager.ts new file mode 100644 index 0000000..6ac83d1 --- /dev/null +++ b/hw3/src/Wolfie2D/Timing/TimerManager.ts @@ -0,0 +1,33 @@ +import Updateable from "../DataTypes/Interfaces/Updateable"; +import Timer from "./Timer"; + +export default class TimerManager implements Updateable { + + protected timers: Array; + + constructor(){ + this.timers = new Array(); + } + + protected static instance: TimerManager; + + static getInstance(): TimerManager { + if(!this.instance){ + this.instance = new TimerManager(); + } + + return this.instance; + } + + addTimer(timer: Timer){ + this.timers.push(timer); + } + + clearTimers(){ + this.timers = new Array(); + } + + update(deltaT: number): void { + this.timers.forEach(timer => timer.update(deltaT)); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/ArrayUtils.ts b/hw3/src/Wolfie2D/Utils/ArrayUtils.ts new file mode 100644 index 0000000..9ee99d4 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/ArrayUtils.ts @@ -0,0 +1,43 @@ +/** A class containing some utility functions for Arrays */ +export default class ArrayUtils { + /** + * Returns a 2d array of dim1 x dim2 filled with 1s + * @param dim1 The first dimension of the array to create + * @param dim2 The second dimension of the array to create + * @returns A dim1 x dim2 Array filled with 1s + */ + static ones2d(dim1: number, dim2: number): number[][] { + let arr = new Array>(dim1); + + for(let i = 0; i < arr.length; i++){ + arr[i] = new Array(dim2); + + for(let j = 0; j < arr[i].length; j++){ + arr[i][j] = 1; + } + } + + return arr; + } + + /** + * Returns a 2d array of dim1 x dim2 filled with true or false + * @param dim1 The first dimension of the array to create + * @param dim2 The second dimension of the array to create + * @param flag The boolean to fill the array with + * @returns A dim1 x dim2 Array filled with flag + */ + static bool2d(dim1: number, dim2: number, flag: boolean): boolean[][] { + let arr = new Array>(dim1); + + for(let i = 0; i < arr.length; i++){ + arr[i] = new Array(dim2); + + for(let j = 0; j < arr[i].length; j++){ + arr[i][j] = flag; + } + } + + return arr; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/Color.ts b/hw3/src/Wolfie2D/Utils/Color.ts new file mode 100644 index 0000000..e7f89d0 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/Color.ts @@ -0,0 +1,197 @@ +import MathUtils from "./MathUtils"; + +// TODO: This should be moved to the datatypes folder +/** + * A Color util class that keeps track of colors like a vector, but can be converted into a string format + */ +export default class Color { + /** The red value */ + public r: number; + /** The green value */ + public g: number; + /** The blue value */ + public b: number; + /** The alpha value */ + public a: number; + + /** + * Creates a new color + * @param r Red + * @param g Green + * @param b Blue + * @param a Alpha + */ + constructor(r: number = 0, g: number = 0, b: number = 0, a: number = 1){ + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Transparent color + * @returns rgba(0, 0, 0, 0) + */ + static get TRANSPARENT(): Color { + return new Color(0, 0, 0, 0); + } + + /** + * Red color + * @returns rgb(255, 0, 0) + */ + static get RED(): Color { + return new Color(255, 0, 0, 1); + } + + /** + * Green color + * @returns rgb(0, 255, 0) + */ + static get GREEN(): Color { + return new Color(0, 255, 0, 1); + } + + /** + * Blue color + * @returns rgb(0, 0, 255) + */ + static get BLUE(): Color { + return new Color(0, 0, 255, 1); + } + + /** + * Yellow color + * @returns rgb(255, 255, 0) + */ + static get YELLOW(): Color { + return new Color(255, 255, 0, 1); + } + + /** + * Magenta color + * @returns rgb(255, 0, 255) + */ + static get MAGENTA(): Color { + return new Color(255, 0, 255, 1); + } + + /** + * Cyan color + * @returns rgb(0, 255, 255) + */ + static get CYAN(): Color { + return new Color(0, 255, 255, 1); + } + + /** + * White color + * @returns rgb(255, 255, 255) + */ + static get WHITE(): Color { + return new Color(255, 255, 255, 1); + } + + /** + * Black color + * @returns rgb(0, 0, 0) + */ + static get BLACK(): Color { + return new Color(0, 0, 0, 1); + } + + /** + * Orange color + * @returns rgb(255, 100, 0) + */ + static get ORANGE(): Color { + return new Color(255, 100, 0, 1); + } + + /** + * Sets the color to the values provided + * @param r Red + * @param g Green + * @param b Blue + * @param a Alpha + */ + set(r: number, g: number, b: number, a: number = 1): void { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + /** + * Returns a new color slightly lighter than the current color + * @returns A new lighter Color + */ + lighten(): Color { + return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), MathUtils.clamp(this.a + 10, 0, 255)); + } + + /** + * Returns a new color slightly darker than the current color + * @returns A new darker Color + */ + darken(): Color { + return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), MathUtils.clamp(this.a + 10, 0, 255)); + } + + /** + * Returns this color as an array + * @returns [r, g, b, a] + */ + toArray(): [number, number, number, number] { + return [this.r, this.g, this.b, this.a]; + } + + /** + * Returns the color as a string of the form #RRGGBB + * @returns #RRGGBB + */ + toString(): string { + return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2); + } + + /** + * Returns the color as a string of the form rgb(r, g, b) + * @returns rgb(r, g, b) + */ + toStringRGB(): string { + return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")"; + } + + /** + * Returns the color as a string of the form rgba(r, g, b, a) + * @returns rgba(r, g, b, a) + */ + toStringRGBA(): string { + if(this.a === 0){ + return this.toStringRGB(); + } + return "rgba(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ", " + this.a.toString() +")" + } + + /** + * Turns this color into a float32Array and changes color range to [0.0, 1.0] + * @returns a Float32Array containing the color + */ + toWebGL(): Float32Array { + return new Float32Array([ + this.r/255, + this.g/255, + this.b/255, + this.a + ]); + } + + static fromStringHex(str: string): Color { + let i = 0; + if(str.charAt(0) == "#") i+= 1; + let r = MathUtils.fromHex(str.substring(i, i+2)); + let g = MathUtils.fromHex(str.substring(i+2, i+4)); + let b = MathUtils.fromHex(str.substring(i+4, i+6)); + return new Color(r, g, b); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/EaseFunctions.ts b/hw3/src/Wolfie2D/Utils/EaseFunctions.ts new file mode 100644 index 0000000..6633c3a --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/EaseFunctions.ts @@ -0,0 +1,55 @@ +// @ignorePage + +export default class EaseFunctions { + + static easeInOutSine(x: number): number { + return -(Math.cos(Math.PI * x) - 1) / 2; + } + + static easeOutInSine(x: number): number { + return x < 0.5 ? -Math.cos(Math.PI*(x + 0.5))/2 : -Math.cos(Math.PI*(x - 0.5))/2 + 1; + } + + static easeOutSine(x: number): number { + return Math.sin((x * Math.PI) / 2); + } + + static easeInSine(x: number): number { + return 1 - Math.cos((x * Math.PI) / 2); + } + + static easeInOutQuint(x: number): number { + return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; + } + + static easeInOutQuad(x: number): number { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + } + + static easeOutInQuad(x: number): number { + return x < 0.5 ? this.easeOutIn_OutPow(x, 2) : this.easeOutIn_InPow(x, 2); + } + + private static easeOutIn_OutPow(x: number, pow: number): number { + return 0.5 - Math.pow(-2 * x + 1, pow) / 2; + } + + private static easeOutIn_InPow(x: number, pow: number): number { + return 0.5 + Math.pow(2 * x - 1, pow) / 2; + } +} + +export enum EaseFunctionType { + // SINE + IN_OUT_SINE = "easeInOutSine", + OUT_IN_SINE = "easeOutInSine", + IN_SINE = "easeInSine", + OUT_SINE = "easeOutSine", + + // QUAD + IN_OUT_QUAD = "easeInOutQuad", + OUT_IN_QUAD = "easeOutInQuad", + + // QUINT + IN_OUT_QUINT = "easeInOutQuint" +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/GraphUtils.ts b/hw3/src/Wolfie2D/Utils/GraphUtils.ts new file mode 100644 index 0000000..774e76d --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/GraphUtils.ts @@ -0,0 +1,64 @@ +import Graph from "../DataTypes/Graphs/Graph"; +import EdgeNode from "../DataTypes/Graphs/EdgeNode"; + +/** A class to provides some utility functions for graphs */ +export default class GraphUtils { + + /** + * An implementation of Djikstra's shortest path algorithm based on the one described in The Algorithm Design Manual. + * @param g The graph + * @param start The number to start the shortest path from + * @returns An array containing the parent of each node of the Graph in the shortest path. + */ + static djikstra(g: Graph, start: number): Array { + let i: number; // Counter + let p: EdgeNode; // Pointer to edgenode + let inTree: Array = new Array(g.numVertices); + let distance: Array = new Array(g.numVertices); + let parent: Array = new Array(g.numVertices); + let v: number; // Current vertex to process + let w: number; // Candidate for next vertex + let weight: number; // Edge weight + let dist; // Best current distance from start + + for(i = 0; i < g.numVertices; i++){ + inTree[i] = false; + distance[i] = Infinity; + parent[i] = -1; + } + + distance[start] = 0; + v = start; + + while(!inTree[v]){ + inTree[v] = true; + p = g.edges[v]; + + while(p !== null){ + w = p.y; + weight = p.weight; + + if(distance[w] > distance[v] + weight){ + distance[w] = distance[v] + weight; + parent[w] = v; + } + + p = p.next; + } + + v = 0; + + dist = Infinity; + + for(i = 0; i <= g.numVertices; i++){ + if(!inTree[i] && dist > distance[i]){ + dist = distance; + v = i; + } + } + } + + return parent; + + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/MathUtils.ts b/hw3/src/Wolfie2D/Utils/MathUtils.ts new file mode 100644 index 0000000..edd1def --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/MathUtils.ts @@ -0,0 +1,175 @@ +import Vec2 from "../DataTypes/Vec2"; + +/** A class containing some utility functions for math operations */ +export default class MathUtils { + /** + * Returns the sign of the value provided + * @param x The value to extract the sign from + * @returns -1 if the number is less than 0, 1 otherwise + */ + static sign(x: number): number { + return x < 0 ? -1 : 1; + } + + /** + * Returns whether or not x is between a and b + * @param a The min bound + * @param b The max bound + * @param x The value to check + * @param exclusive Whether or not a and b are exclusive bounds + * @returns True if x is between a and b, false otherwise + */ + static between(a: number, b: number, x: number, exclusive?: boolean): boolean { + if(exclusive){ + return (a < x) && (x < b); + } else { + return (a <= x) && (x <= b); + } + } + + /** + * Clamps the value x to the range [min, max], rounding up or down if needed + * @param x The value to be clamped + * @param min The min of the range + * @param max The max of the range + * @returns x, if it is between min and max, or min/max if it exceeds their bounds + */ + static clamp(x: number, min: number, max: number): number { + if(x < min) return min; + if(x > max) return max; + return x; + } + + /** + * Clamps the value x to the range between 0 and 1 + * @param x The value to be clamped + * @returns x, if it is between 0 and 1, or 0/1 if it exceeds their bounds + */ + static clamp01(x: number): number { + return MathUtils.clamp(x, 0, 1); + } + + /** + * Clamps the lower end of the value of x to the range to min + * @param x The value to be clamped + * @param min The minimum allowed value of x + * @returns x, if it is greater than min, otherwise min + */ + static clampLow(x: number, min: number): number { + return x < min ? min : x; + } + + /** + * Clamps the lower end of the value of x to zero + * @param x The value to be clamped + * @returns x, if it is greater than 0, otherwise 0 + */ + static clampLow0(x: number): number { + return MathUtils.clampLow(x, 0); + } + + static clampMagnitude(v: Vec2, m: number): Vec2 { + if(v.magSq() > m*m){ + return v.scaleTo(m); + } else{ + return v; + } + } + + static changeRange(x: number, min: number, max: number, newMin: number, newMax: number): number { + return this.lerp(newMin, newMax, this.invLerp(min, max, x)); + } + + /** + * Linear Interpolation + * @param a The first value for the interpolation bound + * @param b The second value for the interpolation bound + * @param t The time we are interpolating to + * @returns The value between a and b at time t + */ + static lerp(a: number, b: number, t: number): number { + return a + t * (b - a); + } + + /** + * Inverse Linear Interpolation. Finds the time at which a value between a and b would occur + * @param a The first value for the interpolation bound + * @param b The second value for the interpolation bound + * @param value The current value + * @returns The time at which the current value occurs between a and b + */ + static invLerp(a: number, b: number, value: number){ + return (value - a)/(b - a); + } + + /** + * Cuts off decimal points of a number after a specified place + * @param num The number to floor + * @param place The last decimal place of the new number + * @returns The floored number + */ + static floorToPlace(num: number, place: number): number { + if(place === 0){ + return Math.floor(num); + } + + let factor = 10; + while(place > 1){ + factor != 10; + place--; + } + + return Math.floor(num*factor)/factor; + + } + + /** + * Returns a number from a hex string + * @param str the string containing the hex number + * @returns the number in decimal represented by the hex string + */ + static fromHex(str: string): number { + return parseInt(str, 16); + } + + /** + * Returns the number as a hexadecimal + * @param num The number to convert to hex + * @param minLength The length of the returned hex string (adds zero padding if needed) + * @returns The hex representation of the number as a string + */ + static toHex(num: number, minLength: number = null): string { + let factor = 1; + while(factor*16 < num){ + factor *= 16; + } + let hexStr = ""; + while(factor >= 1){ + let digit = Math.floor(num/factor); + hexStr += MathUtils.toHexDigit(digit); + num -= digit * factor; + factor /= 16; + } + + if(minLength !== null){ + while(hexStr.length < minLength){ + hexStr = "0" + hexStr; + } + } + + return hexStr; + } + + /** + * Converts a digit to hexadecimal. In this case, a digit is between 0 and 15 inclusive + * @param num The digit to convert to hexadecimal + * @returns The hex representation of the digit as a string + */ + static toHexDigit(num: number): string { + if(num < 10){ + return "" + num; + } else { + return String.fromCharCode(65 + num - 10); + } + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/MemoryUtils.ts b/hw3/src/Wolfie2D/Utils/MemoryUtils.ts new file mode 100644 index 0000000..be5e011 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/MemoryUtils.ts @@ -0,0 +1,46 @@ +import Stack from "../DataTypes/Stack"; + +export default class MemoryUtils { + /** + * Returns an approximate size in bytes of any object + * @param obj The object to get the size of + * @returns An approximation of the number of bytes in the object provided + */ + static approxSizeOf(obj: any): number { + let objectList = new Array(); + let stack = new Stack(10000); + stack.push(obj); + let bytes = 0; + + while(!stack.isEmpty()){ + let item = stack.pop(); + + // We aren't interested in counting window and document + if(item === window || item === document) continue; + + if((typeof item) === "boolean"){ + bytes += 4; + } else if((typeof item) === "number"){ + bytes += 8; + } else if((typeof item) === "string"){ + bytes += item.length; + } else if((typeof item) === "object" && objectList.indexOf(item) === -1){ + // We haven't seen this object before - log it + objectList.push(item); + + // Get the number of bytes in all of its fields + for(let field in item){ + try { + stack.push(item[field]); + } catch(e){ + console.log("Pushing item: ", + item[field]); + console.warn(`Ran out of room counting memory (Stack has size ${stack.size()}) - returning current number of bytes`); + return bytes; + } + } + } + } + + return bytes; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/Rand/Perlin.ts b/hw3/src/Wolfie2D/Utils/Rand/Perlin.ts new file mode 100644 index 0000000..0327e5d --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/Rand/Perlin.ts @@ -0,0 +1,126 @@ +import MathUtils from "../MathUtils"; + +const permutation = [ 151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +]; + +/** + * A noise generator + */ +export default class Perlin { + + private p: Int16Array; + private repeat: number; + + constructor(){ + this.p = new Int16Array(512); + for(let i = 0; i < 512; i++){ + this.p[i] = permutation[i%256]; + } + this.repeat = -1; + } + + /** + * Returns a random perlin noise value + * @param x An input value + * @param y An input value + * @param z An input value + * @returns A noise value + */ + perlin(x: number, y: number, z: number = 0): number { + if(this.repeat > 0) { + x = x%this.repeat; + y = y%this.repeat; + z = z%this.repeat; + } + + // Get the position of the unit cube of (x, y, z) + let xi = Math.floor(x) & 255; + let yi = Math.floor(y) & 255; + let zi = Math.floor(z) & 255; + // Get the position of (x, y, z) in that unit cube + let xf = x - Math.floor(x); + let yf = y - Math.floor(y); + let zf = z - Math.floor(z); + + // Use the fade function to relax the coordinates towards a whole value + let u = this.fade(xf); + let v = this.fade(yf); + let w = this.fade(zf); + + // Perlin noise hash function + let aaa = this.p[this.p[this.p[ xi ]+ yi ]+ zi ]; + let aba = this.p[this.p[this.p[ xi ]+this.inc(yi)]+ zi ]; + let aab = this.p[this.p[this.p[ xi ]+ yi ]+this.inc(zi)]; + let abb = this.p[this.p[this.p[ xi ]+this.inc(yi)]+this.inc(zi)]; + let baa = this.p[this.p[this.p[this.inc(xi)]+ yi ]+ zi ]; + let bba = this.p[this.p[this.p[this.inc(xi)]+this.inc(yi)]+ zi ]; + let bab = this.p[this.p[this.p[this.inc(xi)]+ yi ]+this.inc(zi)]; + let bbb = this.p[this.p[this.p[this.inc(xi)]+this.inc(yi)]+this.inc(zi)]; + + // Calculate the value of the perlin noies + let x1 = MathUtils.lerp(this.grad (aaa, xf , yf , zf), this.grad (baa, xf-1, yf , zf), u); + let x2 = MathUtils.lerp(this.grad (aba, xf , yf-1, zf), this.grad (bba, xf-1, yf-1, zf), u); + let y1 = MathUtils.lerp(x1, x2, v); + + x1 = MathUtils.lerp(this.grad (aab, xf , yf , zf-1), this.grad (bab, xf-1, yf , zf-1), u); + x2 = MathUtils.lerp(this.grad (abb, xf , yf-1, zf-1), this.grad (bbb, xf-1, yf-1, zf-1), u); + let y2 = MathUtils.lerp (x1, x2, v); + + return (MathUtils.lerp(y1, y2, w) + 1)/2; + } + + grad(hash: number, x: number, y: number, z: number){ + switch(hash & 0xF) + { + case 0x0: return x + y; + case 0x1: return -x + y; + case 0x2: return x - y; + case 0x3: return -x - y; + case 0x4: return x + z; + case 0x5: return -x + z; + case 0x6: return x - z; + case 0x7: return -x - z; + case 0x8: return y + z; + case 0x9: return -y + z; + case 0xA: return y - z; + case 0xB: return -y - z; + case 0xC: return y + x; + case 0xD: return -y + z; + case 0xE: return y - x; + case 0xF: return -y - z; + default: return 0; // never happens + } + } + + /** + * Safe increment that doesn't go beyond the repeat value + * @param num The number to increment + */ + inc(num: number){ + num++; + if(this.repeat > 0){ + num %= this.repeat; + } + return num; + } + + /** + * The fade function 6t^5 - 15t^4 + 10t^3 + * @param t The value we are applying the fade to + */ + fade(t: number){ + return t*t*t*(t*(t*6 - 15) + 10); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/RandUtils.ts b/hw3/src/Wolfie2D/Utils/RandUtils.ts new file mode 100644 index 0000000..a1cac17 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/RandUtils.ts @@ -0,0 +1,64 @@ +import MathUtils from "./MathUtils"; +import Color from "./Color"; +import Perlin from "./Rand/Perlin"; +import Vec2 from "../DataTypes/Vec2"; + +class Noise { + p: Perlin = new Perlin(); + + perlin(x: number, y: number, z?: number): number { + return this.p.perlin(x, y, z); + } +} + +/** A class that has some random generator utils */ +export default class RandUtils { + /** + * Generates a random integer in the specified range + * @param min The min of the range (inclusive) + * @param max The max of the range (exclusive) + * @returns A random int in the range [min, max) + */ + static randInt(min: number, max: number): number { + return Math.floor(Math.random()*(max - min) + min); + } + + /** + * Generates a random float in the specified range + * @param min The min of the range (inclusive) + * @param max The max of the range (exclusive) + * @returns A random float in the range [min, max) + */ + static randFloat(min: number, max: number): number { + return Math.random()*(max - min) + min; + } + + /** + * Generates a random hexadecimal number in the specified range + * @param min The min of the range (inclusive) + * @param max The max of the range (exclusive) + * @returns a random hex number in the range [min, max) as a string + */ + static randHex(min: number, max: number): string { + return MathUtils.toHex(RandUtils.randInt(min, max)); + } + + /** + * Generates a random color + * @returns A random Color + */ + static randColor(): Color { + let r = RandUtils.randInt(0, 256); + let g = RandUtils.randInt(0, 256); + let b = RandUtils.randInt(0, 256); + return new Color(r, g, b); + } + + static randVec(minX: number, maxX: number, minY: number, maxY: number): Vec2 { + return new Vec2(this.randFloat(minX, maxX), this.randFloat(minY, maxY)); + } + + /** A noise generator */ + static noise: Noise = new Noise(); + +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/RenderingUtils.ts b/hw3/src/Wolfie2D/Utils/RenderingUtils.ts new file mode 100644 index 0000000..f822227 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/RenderingUtils.ts @@ -0,0 +1,28 @@ +import Vec2 from "../DataTypes/Vec2"; +import Color from "./Color"; +import MathUtils from "./MathUtils"; + +export default class RenderingUtils { + static toWebGLCoords(point: Vec2, origin: Vec2, worldSize: Vec2): Float32Array { + return new Float32Array([ + MathUtils.changeRange(point.x, origin.x, origin.x + worldSize.x, -1, 1), + MathUtils.changeRange(point.y, origin.y, origin.y + worldSize.y, 1, -1) + ]); + } + + static toWebGLScale(size: Vec2, worldSize: Vec2): Float32Array { + return new Float32Array([ + 2*size.x/worldSize.x, + 2*size.y/worldSize.y, + ]); + } + + static toWebGLColor(color: Color): Float32Array { + return new Float32Array([ + MathUtils.changeRange(color.r, 0, 255, 0, 1), + MathUtils.changeRange(color.g, 0, 255, 0, 1), + MathUtils.changeRange(color.b, 0, 255, 0, 1), + color.a + ]); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/SortingUtils.ts b/hw3/src/Wolfie2D/Utils/SortingUtils.ts new file mode 100644 index 0000000..b5eec67 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/SortingUtils.ts @@ -0,0 +1,37 @@ +/** Some utility functions for sorting arrays */ +export default class SortingUtils { + /** + * An implementation of insertion sort. + * In game engines, this is particularly useful to sort node positions because of temporal coherence - + * the idea that nodes are almost in the same place as last frame, and thus, in a frame-to-frame comparison, + * nodes essentially do not change position. + * This means we have a nearly sorted array of nodes if we keep track of this, + * so something like insertion sort actually becomes essentailly O(n), + * as it performs very well on nearly sorted arrays. + * @param arr The array to sort in place + * @param comparator Compares element a and b in the array. Returns -1 if a < b, 0 if a = b, and 1 if a > b + */ + static insertionSort(arr: Array, comparator: (a: T, b: T) => number): void { + let i = 1; + let j; + while(i < arr.length){ + j = i; + while(j > 0 && comparator(arr[j-1], arr[j]) > 0){ + SortingUtils.swap(arr, j-1, j); + } + i += 1; + } + } + + /** + * Swaps two elements in the provided array + * @param arr The array to perform the swap on in place + * @param i The first index + * @param j The second index + */ + static swap(arr: Array, i: number, j: number): void { + let temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/Utils/StringUtils.ts b/hw3/src/Wolfie2D/Utils/StringUtils.ts new file mode 100644 index 0000000..672c050 --- /dev/null +++ b/hw3/src/Wolfie2D/Utils/StringUtils.ts @@ -0,0 +1,14 @@ +/** Some utility functions for dealing with strings */ +export default class StringUtils { + /** + * Extracts the path from a filepath that includes the file + * @param filePath the filepath to extract the path from + * @returns The path portion of the filepath provided + */ + static getPathFromFilePath(filePath: string): string { + let splitPath = filePath.split("/"); + splitPath.pop(); + splitPath.push(""); + return splitPath.join("/"); + } +} \ No newline at end of file diff --git a/hw3/src/Wolfie2D/_Demos/readme.md b/hw3/src/Wolfie2D/_Demos/readme.md new file mode 100644 index 0000000..6086b2f --- /dev/null +++ b/hw3/src/Wolfie2D/_Demos/readme.md @@ -0,0 +1,2 @@ +# Demos +This folder contains the demo projects created in the guides section of the Wolfie2D documentation, as well as any extra demos created for Wolfie2D. \ No newline at end of file diff --git a/hw3/src/default_main.ts b/hw3/src/default_main.ts new file mode 100644 index 0000000..32278ae --- /dev/null +++ b/hw3/src/default_main.ts @@ -0,0 +1,23 @@ +import Game from "./Wolfie2D/Loop/Game"; +import default_scene from "./default_scene"; + +// The main function is your entrypoint into Wolfie2D. Specify your first scene and any options here. +(function main(){ + // Run any tests + runTests(); + + // Set up options for our game + let options = { + canvasSize: {x: 1200, y: 800}, // The size of the game + clearColor: {r: 0, g: 0, b: 0}, // The color the game clears to + useWebGL: true + } + + // Create a game with the options specified + const game = new Game(options); + + // Start our game + game.start(default_scene, {}); +})(); + +function runTests(){}; \ No newline at end of file diff --git a/hw3/src/default_scene.ts b/hw3/src/default_scene.ts new file mode 100644 index 0000000..9281de1 --- /dev/null +++ b/hw3/src/default_scene.ts @@ -0,0 +1,98 @@ +/* #################### IMPORTS #################### */ +// Import from Wolfie2D or your own files here +import Vec2 from "./Wolfie2D/DataTypes/Vec2"; +import Input from "./Wolfie2D/Input/Input"; +import Graphic from "./Wolfie2D/Nodes/Graphic"; +import { GraphicType } from "./Wolfie2D/Nodes/Graphics/GraphicTypes"; +import Sprite from "./Wolfie2D/Nodes/Sprites/Sprite"; +import Scene from "./Wolfie2D/Scene/Scene"; +import Color from "./Wolfie2D/Utils/Color"; + + +/* #################### CLASS DEFINITION #################### */ + +// Welcome to Wolfie2D! +// This is a simple sample scene so something displays when you run the game. +export default class default_scene extends Scene { + /* ########## MEMBER DEFINITIONS ##########*/ + private logo: Sprite; + private player: Graphic; + + /* ########## BUILT-IN FUNCTIONS ########## */ + // The following are built-in abstract Scene functions you are meant to extend. + // They represent the lifecycle of a Scene, and thus allow you to load and start your scene + + // loadScene() is where you should load initial assets needed for your scene. + // it is called strictly before startScene, so you are assured all assets will be loaded before + // the scene begins + loadScene(): void { + // Load any assets here. For example, to load an image (or a sprite): + + // The first argument is the key of the sprite (you get to decide what it is). + // The second argument is the path to the actual image. + // Paths start in the "dist/" folder, so start building your path from there + this.load.image("logo", "demo_assets/images/wolfie2d_text.png"); + } + + // startScene() is where you should build any game objects you wish to have in your scene, + // or where you should initialize any other things you will need in your scene + // Once again, this occurs strictly after loadScene(), so anything you loaded there will be available + startScene(): void { + // Create any game objects here. For example, to add the sprite we previously loaded: + + // First, create a layer for it to go on + this.addLayer("primary"); + + // The first argument is the key we specified in "this.load.image" + // The second argument is the name of the layer + this.logo = this.add.sprite("logo", "primary"); + + // Now, let's make sure our logo is in a good position + let center = this.viewport.getCenter(); + this.logo.position.set(center.x, center.y); + + + + // We can also create game objects (such as graphics and UIElements) without using loaded assets + // Lets add a rectangle to use as the player object + // For some game objects, you have to specify an options object. In this case, position and size: + let options = { + size: new Vec2(50, 50), + position: new Vec2(center.x, center.y + 100) + } + + // Create the rect + this.player = this.add.graphic(GraphicType.RECT, "primary", options); + + // Now, let's change the color of our player + this.player.color = Color.ORANGE; + } + + // updateScene() is where you can handle any frame by frame updates to your scene. + // For the most part, GameNode logic can be abstracted to AI, but there may be + // things you want to control for the whole scene, like player score. + // The argument to updateScene is the time step of the update frame + updateScene(deltaT: number): void { + // For our update, lets create a basic player controller + // First, lets handle the input + const direction = Vec2.ZERO; + + // Sum the x-direction keys + direction.x = (Input.isKeyPressed("a") ? -1 : 0) + (Input.isKeyPressed("d") ? 1 : 0); + + // Sum the y-direction keys + direction.y = (Input.isKeyPressed("w") ? -1 : 0) + (Input.isKeyPressed("s") ? 1 : 0); + + // We don't want to move faster in diagonals, so normalize + direction.normalize(); + + // We want to move 100 units per second, not per frame, so multiply by deltaT when moving + const speed = 100 * deltaT; + + // Scale our direction to speed + const velocity = direction.scale(speed); + + // Finally, adjust the position of the player + this.player.position.add(velocity); + } +} \ No newline at end of file diff --git a/hw3/src/demos/AudioDemo.ts b/hw3/src/demos/AudioDemo.ts new file mode 100644 index 0000000..9b5c58f --- /dev/null +++ b/hw3/src/demos/AudioDemo.ts @@ -0,0 +1,71 @@ +import Vec2 from "../Wolfie2D/DataTypes/Vec2"; +import { GameEventType } from "../Wolfie2D/Events/GameEventType"; +import Input from "../Wolfie2D/Input/Input"; +import Label from "../Wolfie2D/Nodes/UIElements/Label"; +import Slider from "../Wolfie2D/Nodes/UIElements/Slider"; +import { UIElementType } from "../Wolfie2D/Nodes/UIElements/UIElementTypes"; +import Scene from "../Wolfie2D/Scene/Scene"; +import AudioManager, { AudioChannelType } from "../Wolfie2D/Sound/AudioManager"; +import Color from "../Wolfie2D/Utils/Color"; + +export default class Test extends Scene { + loadScene(){ + this.load.audio("song", "demo_assets/sounds/title.mp3"); + this.load.audio("sfx", "demo_assets/sounds/jump.wav"); + } + + startScene(){ + this.emitter.fireEvent(GameEventType.PLAY_MUSIC, {key: "song", loop: true, holdReference: true}); + + this.addLayer("Main"); + + // Initialize value to 1 (music is at max) + let slider = this.add.uiElement(UIElementType.SLIDER, "Main", {position: new Vec2(600, 600), value: 1}); + + // UI Stuff + slider.size = new Vec2(200, 50); + slider.nibSize = new Vec2(30, 30); + slider.nibColor = Color.WHITE; + slider.sliderColor = Color.WHITE; + + slider.onValueChange = (value: number) => { + // Use a non-linear value->volume function, since sound is wack + AudioManager.setVolume(AudioChannelType.MUSIC, value*value); + } + + // Initialize value to 1 (music is at max) + let sfxslider = this.add.uiElement(UIElementType.SLIDER, "Main", {position: new Vec2(600, 700), value: 1}); + + // UI Stuff + sfxslider.size = new Vec2(200, 50); + sfxslider.nibSize = new Vec2(30, 30); + sfxslider.nibColor = Color.WHITE; + sfxslider.sliderColor = Color.WHITE; + + sfxslider.onValueChange = (value: number) => { + // Use a non-linear value->volume function, since sound is wack + AudioManager.setVolume(AudioChannelType.SFX, value*value); + } + + (this.add.uiElement(UIElementType.LABEL, "Main", {position: new Vec2(600, 100), text: "1 - Play a sound"}) as Label).textColor = Color.WHITE; + (this.add.uiElement(UIElementType.LABEL, "Main", {position: new Vec2(600, 200), text: "2 - Mute music"}) as Label).textColor = Color.WHITE; + (this.add.uiElement(UIElementType.LABEL, "Main", {position: new Vec2(600, 300), text: "3 - Unmute music"}) as Label).textColor = Color.WHITE; + (this.add.uiElement(UIElementType.LABEL, "Main", {position: new Vec2(600, 400), text: "4 - Fade out music"}) as Label).textColor = Color.WHITE; + } + + updateScene(deltaT: number){ + if(Input.isKeyJustPressed("1")){ + this.emitter.fireEvent(GameEventType.PLAY_SFX, {key: "sfx", loop: false, holdReference: false}); + } else if(Input.isKeyJustPressed("2")){ + this.emitter.fireEvent(GameEventType.MUTE_CHANNEL, {channel: AudioChannelType.MUSIC}); + } else if(Input.isKeyJustPressed("3")){ + this.emitter.fireEvent(GameEventType.UNMUTE_CHANNEL, {channel: AudioChannelType.MUSIC}); + } else if(Input.isKeyJustPressed("4")){ + // https://developer.mozilla.org/en-US/docs/Web/API/AudioParam + const am = AudioManager.getInstance(); + const ctx = am.getAudioContext(); + const gainNode = am.getChannelGainNode(AudioChannelType.MUSIC); + gainNode.gain.setTargetAtTime(0, ctx.currentTime, 0.5); + } + } +} \ No newline at end of file diff --git a/hw3/src/demos/Platformer.ts b/hw3/src/demos/Platformer.ts new file mode 100644 index 0000000..9eb409a --- /dev/null +++ b/hw3/src/demos/Platformer.ts @@ -0,0 +1,49 @@ +import PlayerController from "./PlatformerPlayerController"; +import Vec2 from "../Wolfie2D/DataTypes/Vec2"; +import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import Scene from "../Wolfie2D/Scene/Scene"; + +export default class Platformer extends Scene { + private player: AnimatedSprite; + + // Load any assets you will need for the project here + loadScene(){ + // Load the player spritesheet + this.load.spritesheet("player", "demo_assets/spritesheets/platformer/player.json"); + + // Load the tilemap + this.load.tilemap("platformer", "demo_assets/tilemaps/platformer/platformer.json"); + + // Load the background image + this.load.image("background", "demo_assets/images/platformer_background.png"); + + // Load a jump sound + this.load.audio("jump", "demo_assets/sounds/jump.wav"); + } + + // Add GameObjects to the scene + startScene(){ + this.addLayer("primary", 1); + + // Add the player in the starting position + this.player = this.add.animatedSprite("player", "primary"); + this.player.animation.play("IDLE"); + this.player.position.set(3*16, 18*16); + + // Add physics so the player can move + this.player.addPhysics(); + this.player.addAI(PlayerController, {jumpSoundKey: "jump"}); + + // Size of the tilemap is 64x20. Tile size is 16x16 + this.viewport.setBounds(0, 0, 64*16, 20*16); + this.viewport.follow(this.player); + + // Add the tilemap. Top left corner is (0, 0) by default + this.add.tilemap("platformer"); + + // Add a background to the scene + this.addParallaxLayer("bg", new Vec2(0.5, 1), -1); + let bg = this.add.sprite("background", "bg"); + bg.position.set(bg.size.x/2, bg.size.y/2); + } +} \ No newline at end of file diff --git a/hw3/src/demos/PlatformerPlayerController.ts b/hw3/src/demos/PlatformerPlayerController.ts new file mode 100644 index 0000000..60d7be7 --- /dev/null +++ b/hw3/src/demos/PlatformerPlayerController.ts @@ -0,0 +1,62 @@ +import ControllerAI from "../Wolfie2D/AI/ControllerAI"; +import AI from "../Wolfie2D/DataTypes/Interfaces/AI"; +import Emitter from "../Wolfie2D/Events/Emitter"; +import GameEvent from "../Wolfie2D/Events/GameEvent"; +import { GameEventType } from "../Wolfie2D/Events/GameEventType"; +import Input from "../Wolfie2D/Input/Input"; +import AnimatedSprite from "../Wolfie2D/Nodes/Sprites/AnimatedSprite"; + +export default class PlayerController extends ControllerAI { + public owner: AnimatedSprite; + protected jumpSoundKey: string; + protected emitter: Emitter; + + initializeAI(owner: AnimatedSprite, options: Record): void { + this.owner = owner; + this.jumpSoundKey = options.jumpSoundKey; + this.emitter = new Emitter(); + } + + activate(options: Record): void {} + + handleEvent(event: GameEvent): void { + // Do nothing for now + } + + update(deltaT: number): void { + // Get the direction from key presses + const x = (Input.isPressed("left") ? -1 : 0) + (Input.isPressed("right") ? 1 : 0); + + // Get last velocity and override x + const velocity = this.owner.getLastVelocity(); + velocity.x = x * 100 * deltaT; + + // Check for jump condition + if(this.owner.onGround && Input.isJustPressed("jump")){ + // We are jumping + velocity.y = -250*deltaT; + + // Loop our jump animation + this.owner.animation.play("JUMP", true); + + // Play the jump sound + this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: this.jumpSoundKey, loop: false}); + } else { + velocity.y += 10*deltaT; + } + + if(this.owner.onGround && !Input.isJustPressed("jump")){ + // If we're on the ground, but aren't jumping, show walk animation + if(velocity.x === 0){ + this.owner.animation.playIfNotAlready("IDLE", true); + } else { + this.owner.animation.playIfNotAlready("WALK", true); + } + } + + // If we're walking left, flip the sprite + this.owner.invertX = velocity.x < 0; + + this.owner.move(velocity); + } +} \ No newline at end of file diff --git a/hw3/src/hw3/AI/BulletAI.ts b/hw3/src/hw3/AI/BulletAI.ts new file mode 100644 index 0000000..907930f --- /dev/null +++ b/hw3/src/hw3/AI/BulletAI.ts @@ -0,0 +1,72 @@ +import AI from "../../Wolfie2D/DataTypes/Interfaces/AI"; +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Emitter from "../../Wolfie2D/Events/Emitter"; +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import Receiver from "../../Wolfie2D/Events/Receiver"; +import Graphic from "../../Wolfie2D/Nodes/Graphic"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import MathUtils from "../../Wolfie2D/Utils/MathUtils"; +import { Homework3Event, Homework3Animations } from "../HW3_Enums"; + +/** + * This class controls our bullet behavior. Bullets will start out at a certain speed and then accelerate until they either + * hit a obstacle or leave the screen. + */ +export default class BulletBehavior implements AI { + // The owner of this AI + private owner: Graphic; + + // The velocity + private current_speed: number; + private start_speed: number; + + // Some vars to keep put bounds on the speed of the bullet + static SPEED_INC: number = 75; + static MAX_SPEED: number = 700; + + // An event emitter and receiver to hook into the event system + private receiver: Receiver + + initializeAI(owner: Graphic, options: Record): void { + this.owner = owner; + + this.current_speed = options.speed;; + + this.receiver = new Receiver(); + + this.receiver.subscribe(Homework3Event.BULLET_USED); + } + + activate(options: Record): void { + this.start_speed = options.speed; + this.current_speed = this.start_speed; + } + + + handleEvent(event: GameEvent): void { + // If the bullet used was the same as this bullet, then reset the speed + if (event.data.get("id") == this.owner.id) { + this.current_speed = this.start_speed; + } + } + + update(deltaT: number): void { + while(this.receiver.hasNextEvent()){ + this.handleEvent(this.receiver.getNextEvent()); + } + + if(this.owner.visible){ + //While this bullet is active, accelerate the bullet to a max speed over time. + this.current_speed += deltaT * BulletBehavior.SPEED_INC; + this.current_speed = MathUtils.clamp(this.current_speed, this.start_speed, BulletBehavior.MAX_SPEED); + + // Update the position + this.owner.position.add(Vec2.UP.scaled(deltaT * this.current_speed)); + } + } + + destroy(): void { + + } + +} \ No newline at end of file diff --git a/hw3/src/hw3/AI/CarPlayerController.ts b/hw3/src/hw3/AI/CarPlayerController.ts new file mode 100644 index 0000000..f4086db --- /dev/null +++ b/hw3/src/hw3/AI/CarPlayerController.ts @@ -0,0 +1,110 @@ +import AI from "../../Wolfie2D/DataTypes/Interfaces/AI"; +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Debug from "../../Wolfie2D/Debug/Debug"; +import Emitter from "../../Wolfie2D/Events/Emitter"; +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import Receiver from "../../Wolfie2D/Events/Receiver"; +import Input from "../../Wolfie2D/Input/Input"; +import AnimatedSprite from "../../Wolfie2D/Nodes/Sprites/AnimatedSprite"; +import MathUtils from "../../Wolfie2D/Utils/MathUtils"; +import { Homework3Event } from "../HW3_Enums"; + +export default class CarPlayerController implements AI { + // We want to be able to control our owner, so keep track of them + private owner: AnimatedSprite; + + // The speed the car is moving + private MIN_SPEED: number = 300; + private MAX_SPEED: number = 500; + private speed: number; + + private isDead: boolean = false; + + // A receiver and emitter to hook into the event queue + private receiver: Receiver; + private emitter: Emitter; + + // HOMEWORK 3 + /** + * This method initializes all variables inside of this AI class, and sets + * up anything we need it do. + * + * You should subscribe to the correct event for player damage here using the Receiver. + * The AI will react to the event in handleEvent() - you just need to make sure + * it is subscribed to them. + * + * Also note the names of animations when calling this.owner.animation.play, you do not need to implement these parts but + * note that you either need to adjust the names of the animations to what you have or rename the animations where appropriate. + * + * @param owner The owner of this AI - i.e. the player + * @param options The list of options for ai initialization + */ + initializeAI(owner: AnimatedSprite, options: Record): void { + this.owner = owner; + + //Start with MIN_SPEED + this.speed = this.MIN_SPEED; + + this.receiver = new Receiver(); + this.emitter = new Emitter(); + } + + activate(options: Record){}; + + handleEvent(event: GameEvent): void { + // We need to handle animations when we get hurt + if(event.type === Homework3Event.PLAYER_DAMAGE){ + if(event.data.get("health") === 0){ + // Play animation and queue event to end game + this.owner.animation.play("dying", false, Homework3Event.PLAYER_DEAD); + this.owner.animation.queue("dead", true); + this.isDead = true; + } else { + this.owner.animation.play("damage", false, Homework3Event.PLAYER_I_FRAMES_END); + } + } + } + + update(deltaT: number): void { + if(this.isDead) return; + + while(this.receiver.hasNextEvent()){ + this.handleEvent(this.receiver.getNextEvent()); + } + + //HOMEWORK 3 + //When the player clicks their mouse, a bullet should be fired by using the SHOOT_BULLET event. + //Note that you shouldn't be able to fire a bullet while holding shift. + + + //If shift is currently being held down, increase the speed of the car. If not, check if mouse click has been pressed to shoot a bullet. + if(Input.isKeyPressed("shift")) { + this.speed = this.MAX_SPEED; + } + else { + if (Input.isMouseJustPressed() || Input.isKeyJustPressed("j")) { + this.owner.animation.play("firing", false, Homework3Event.PLAYER_I_FRAMES_END); + this.emitter.fireEvent(Homework3Event.SHOOT_BULLET, {position: this.owner.position.clone()}); + } + this.speed = this.MIN_SPEED; + } + + // We need to handle player input for movement + let forwardAxis = (Input.isPressed('forward') ? 1 : 0) + (Input.isPressed('backward') ? -1 : 0); + let horizontalAxis = (Input.isPressed('left') ? -1 : 0) + (Input.isPressed('right') ? 1 : 0); + + let movement = Vec2.UP.scaled(forwardAxis * this.speed); + movement = movement.add(new Vec2(horizontalAxis * this.speed, 0)); + + // Move the player + this.owner.position.add(movement.scaled(deltaT)); + + // Animations + if(!this.owner.animation.isPlaying("damage") && !this.owner.animation.isPlaying("dying") && !this.owner.animation.isPlaying("firing")){ + this.owner.animation.playIfNotAlready("driving"); + } + } + destroy(): void { + + } +} diff --git a/hw3/src/hw3/AI/RockAI.ts b/hw3/src/hw3/AI/RockAI.ts new file mode 100644 index 0000000..3361dbd --- /dev/null +++ b/hw3/src/hw3/AI/RockAI.ts @@ -0,0 +1,39 @@ +import AI from "../../Wolfie2D/DataTypes/Interfaces/AI"; +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Debug from "../../Wolfie2D/Debug/Debug"; +import GameEvent from "../../Wolfie2D/Events/GameEvent"; +import GameNode from "../../Wolfie2D/Nodes/GameNode"; +import Graphic from "../../Wolfie2D/Nodes/Graphic"; + +export default class RockAI implements AI { + // The owner of this AI + protected owner: Graphic; + + // The direction of an rock + public direction: Vec2; + + // The speed all rocks move at + public static SPEED: number = 10; + + initializeAI(owner: Graphic, options: Record): void { + this.owner = owner; + this.direction = Vec2.ZERO; + } + + activate(options: Record): void { + this.direction = options.direction; + } + + handleEvent(event: GameEvent): void { + // Do nothing + } + + update(deltaT: number): void { + if(this.owner.visible) + this.owner.position.add(Vec2.DOWN.scaled(RockAI.SPEED * deltaT)); + } + + destroy(): void { + + } +} \ No newline at end of file diff --git a/hw3/src/hw3/GradientCircleShaderType.ts b/hw3/src/hw3/GradientCircleShaderType.ts new file mode 100644 index 0000000..c5827d7 --- /dev/null +++ b/hw3/src/hw3/GradientCircleShaderType.ts @@ -0,0 +1,80 @@ +import Map from "../Wolfie2D/DataTypes/Map"; +import Mat4x4 from "../Wolfie2D/DataTypes/Mat4x4"; +import Vec2 from "../Wolfie2D/DataTypes/Vec2"; +import Graphic from "../Wolfie2D/Nodes/Graphic"; +import Rect from "../Wolfie2D/Nodes/Graphics/Rect"; +import RectShaderType from "../Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType"; +import Color from "../Wolfie2D/Utils/Color"; + + +export default class GradientCircleShaderType extends RectShaderType { + + initBufferObject(): void { + this.bufferObjectKey = "gradient_circle"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + render(gl: WebGLRenderingContext, options: Record): void { + // Get our program and buffer object + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + + // Let WebGL know we're using our shader program + gl.useProgram(program); + + // Get our vertex data + const vertexData = this.getVertices(options.size.x, options.size.y); + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + /* ##### ATTRIBUTES ##### */ + // No texture, the only thing we care about is vertex position + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + /* ##### UNIFORMS ##### */ + + // Get transformation matrix + // We have a square for our rendering space, so get the maximum dimension of our quad + let maxDimension = Math.max(options.size.x, options.size.y); + + // The size of the rendering space will be a square with this maximum dimension + let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y); + + // Center our translations around (0, 0) + const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension; + const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension; + + // Create our transformation matrix + this.translation.translate(new Float32Array([translateX, translateY])); + this.scale.scale(size); + this.rotation.rotate(options.rotation); + let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation); + + // Pass the translation matrix to our shader + const u_Transform = gl.getUniformLocation(program, "u_Transform"); + gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); + + //color + let webGL_color = options.color.toWebGL(); + const circle_Color = gl.getUniformLocation(program, "circle_Color"); + gl.uniform4f(circle_Color, webGL_color[0], webGL_color[1], webGL_color[2], webGL_color[3]); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + getOptions(gc: Rect): Record { + let options: Record = { + position: gc.position, + size: gc.size, + rotation: gc.rotation, + color: gc.color + } + return options; + } +} \ No newline at end of file diff --git a/hw3/src/hw3/HW3_Enums.ts b/hw3/src/hw3/HW3_Enums.ts new file mode 100644 index 0000000..4bb0770 --- /dev/null +++ b/hw3/src/hw3/HW3_Enums.ts @@ -0,0 +1,23 @@ +export enum Homework3Event { + PLAY_GAME = "PLAY_GAME", + CONTROLS = "CONTROLS", + ABOUT = "ABOUT", + MENU = "MENU", + + PLAYER_DAMAGE = "PLAYER_DAMAGE", + PLAYER_I_FRAMES_END = "PLAYER_I_FRAMES_END", + PLAYER_DEAD = "PLAYER_DEAD", + SHOOT_BULLET = "SHOOT_BULLET", + BULLET_USED = "BULLET_USED" +} + +export enum Homework3Shaders { + GRADIENT_CIRCLE = "GRADIENT_CIRCLE", + LINEAR_GRADIENT_CIRCLE = "LINEAR_GRADIENT_CIRCLE" +} + +export enum Homework3Animations { + CAR_IDLE = "idle", + CAR_BOOST = "boost", + CAR_DIE = "explode" +} \ No newline at end of file diff --git a/hw3/src/hw3/LinearGradientCircleShaderType.ts b/hw3/src/hw3/LinearGradientCircleShaderType.ts new file mode 100644 index 0000000..6c2b8bc --- /dev/null +++ b/hw3/src/hw3/LinearGradientCircleShaderType.ts @@ -0,0 +1,109 @@ +import Map from "../Wolfie2D/DataTypes/Map"; +import Mat4x4 from "../Wolfie2D/DataTypes/Mat4x4"; +import Vec2 from "../Wolfie2D/DataTypes/Vec2"; +import Graphic from "../Wolfie2D/Nodes/Graphic"; +import Rect from "../Wolfie2D/Nodes/Graphics/Rect"; +import RectShaderType from "../Wolfie2D/Rendering/WebGLRendering/ShaderTypes/RectShaderType"; +import Color from "../Wolfie2D/Utils/Color"; + +/** + * The linear gradient circle is technically rendered on a quad, and is similar to a rect, so we'll extend the RectShaderType + */ +export default class LinearGradientCircleShaderType extends RectShaderType { + + initBufferObject(): void { + this.bufferObjectKey = "linear_gradient_circle"; + this.resourceManager.createBuffer(this.bufferObjectKey); + } + + // HOMEWORK 3 + /** + * You should modify this method to pass another color into LinearGradientCircles + * + * Think about the best way to pass data to the shader: + * Is color static throughout the shader program (uniforms)? Or does it depend on the vertex (attributes)? + * Is color interpolated depending on the position of the fragment between vertices (varying)? + * + * You may look at the variables passed to the shader in this render function, as well as those + * in other ShaderTypes. + * + * @param gl The rendering context + * @param options The options object received from the getOptions() method + */ + render(gl: WebGLRenderingContext, options: Record): void { + // Get our program and buffer object + const program = this.resourceManager.getShaderProgram(this.programKey); + const buffer = this.resourceManager.getBuffer(this.bufferObjectKey); + + // Let WebGL know we're using our shader program + gl.useProgram(program); + + // Get our vertex data + const vertexData = this.getVertices(options.size.x, options.size.y); + const FSIZE = vertexData.BYTES_PER_ELEMENT; + + // Bind the buffer + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); + + /* ##### ATTRIBUTES ##### */ + // No texture, the only thing we care about is vertex position + const a_Position = gl.getAttribLocation(program, "a_Position"); + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0 * FSIZE); + gl.enableVertexAttribArray(a_Position); + + /* ##### UNIFORMS ##### */ + + // Get transformation matrix + // We have a square for our rendering space, so get the maximum dimension of our quad + let maxDimension = Math.max(options.size.x, options.size.y); + + // The size of the rendering space will be a square with this maximum dimension + let size = new Vec2(maxDimension, maxDimension).scale(2/options.worldSize.x, 2/options.worldSize.y); + + // Center our translations around (0, 0) + const translateX = (options.position.x - options.origin.x - options.worldSize.x/2)/maxDimension; + const translateY = -(options.position.y - options.origin.y - options.worldSize.y/2)/maxDimension; + + // Create our transformation matrix + this.translation.translate(new Float32Array([translateX, translateY])); + this.scale.scale(size); + this.rotation.rotate(options.rotation); + let transformation = Mat4x4.MULT(this.translation, this.scale, this.rotation); + + // Pass the translation matrix to our shader + const u_Transform = gl.getUniformLocation(program, "u_Transform"); + gl.uniformMatrix4fv(u_Transform, false, transformation.toArray()); + + //color + + + let webGL_color = options.color.toWebGL(); + const circle_Color = gl.getUniformLocation(program, "circle_Color"); + gl.uniform4f(circle_Color, webGL_color[0], webGL_color[1], webGL_color[2], webGL_color[3]); + + let default_color = Color.BLUE.toWebGL(); + const default_Color = gl.getUniformLocation(program, "default_Color"); + gl.uniform4f(default_Color, default_color[0], default_color[1], default_color[2], default_color[3]); + + // Draw the quad + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + // HOMEWORK 3 + /** + * This method decides what options get passed to the above render() method. + * You should modify this class to allow you to pass another color into the render method. + * The second color you pass in should be blue, and this can be hardcoded into this method since + * Rect only has 1 color field. + */ + getOptions(gc: Rect): Record { + let options: Record = { + position: gc.position, + size: gc.size, + rotation: gc.rotation, + color: gc.color, + } + return options; + } +} \ No newline at end of file diff --git a/hw3/src/hw3/Scenes/GameOver.ts b/hw3/src/hw3/Scenes/GameOver.ts new file mode 100644 index 0000000..e51b4c3 --- /dev/null +++ b/hw3/src/hw3/Scenes/GameOver.ts @@ -0,0 +1,41 @@ +import Vec2 from "../../Wolfie2D/DataTypes/Vec2"; +import Input from "../../Wolfie2D/Input/Input"; +import Label from "../../Wolfie2D/Nodes/UIElements/Label"; +import { UIElementType } from "../../Wolfie2D/Nodes/UIElements/UIElementTypes"; +import Scene from "../../Wolfie2D/Scene/Scene"; +import Color from "../../Wolfie2D/Utils/Color"; +import MainMenu from "./MainMenu"; + +export default class GameOver extends Scene { + private score: number; + private minerals: number; + + initScene(options: Record){ + this.score = options.score; + this.minerals = options.minerals; + } + + startScene() { + const center = this.viewport.getCenter(); + + this.addUILayer("primary"); + + const gameOver =