diff --git a/code/easing.js b/code/easing.js new file mode 100644 index 0000000..267cb78 --- /dev/null +++ b/code/easing.js @@ -0,0 +1,175 @@ +export var easings = { // Easings taken from https://easings.net/ + linear: (x) => { + return x; + }, + easeInSine: (x) => { + return 1 - Math.cos((x * Math.PI) / 2); + }, + easeOutSine: (x) => { + return Math.sin((x * Math.PI) / 2); + }, + easeInOutSine: (x) => { + return -(Math.cos(Math.PI * x) - 1) / 2; + }, + easeInQuad: (x) => { + return x * x; + }, + easeOutQuad: (x) => { + return 1 - (1 - x) * (1 - x); + }, + easeInOutQuad: (x) => { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + }, + easeInCubic: (x) => { + return x * x * x; + }, + easeOutCubic: (x) => { + return 1 - Math.pow(1 - x, 3); + }, + easeInOutCubic: (x) => { + return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + }, + easeInQuart: (x) => { + return x * x * x * x; + }, + easeOutQuart: (x) => { + return 1 - Math.pow(1 - x, 4); + }, + easeInOutQuart: (x) => { + return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; + }, + easeInQuint: (x) => { + return x * x * x * x * x; + }, + easeOutQuint: (x) => { + return 1 - Math.pow(1 - x, 5); + }, + easeInOutQuint: (x) => { + return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; + }, + easeInExpo: (x) => { + return x === 0 ? 0 : Math.pow(2, 10 * x - 10); + }, + easeOutExpo: (x) => { + return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); + }, + easeInOutExpo: (x) => { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 + : (2 - Math.pow(2, -20 * x + 10)) / 2; + }, + easeInCirc: (x) => { + return 1 - Math.sqrt(1 - Math.pow(x, 2)); + }, + easeOutCirc: (x) => { + return Math.sqrt(1 - Math.pow(x - 1, 2)); + }, + easeInOutCirc: (x) => { + return x < 0.5 + ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; + }, + easeInBack: (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + return c3 * x * x * x - c1 * x * x; + }, + easeOutBack: (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }, + easeInOutBack: (x) => { + const c1 = 1.70158; + const c2 = c1 * 1.525; + return x < 0.5 + ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 + : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + }, + easeInBounce: (x) => { + return 1 - easings.easeOutBounce(1 - x); + }, + easeOutBounce: (x) => { + const n1 = 7.5625; + const d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + }, + easeInOutBounce: (x) => { + return x < 0.5 + ? (1 - easings.easeOutBounce(1 - 2 * x)) / 2 + : (1 + easings.easeOutBounce(2 * x - 1)) / 2; + } +} + +export var tweentypes = { + FOREVER: (t, st, tl) => { + if (t - st >= tl) { + return "FOREVER"; + } + return "CONTINUE"; + }, + LERPFOREVER: (t, st, tl) => { + return "LF"; + }, + PINGPONG: (t, st, tl) => { + if (t - st >= tl) { + return "PING"; + } + return "CONTINUE"; + }, + NORMAL: (t, st, tl) => { + if (t - st >= tl) { + return "CALLBACK"; + } + return "CONTINUE"; + } +} + +export function tween(func, attrs, timeLen, minVal, maxVal, ease, type, onFinish) { + var minVal = minVal != undefined ? minVal : func[attrs[0]]; + var ease = ease != undefined ? ease : easings.linear; + var type = type != undefined ? type : tweentypes.NORMAL; + var stTime = time(); + var onFinish = onFinish != undefined ? onFinish : "ud"; + var upd = onUpdate(() => { + switch (type(time(), stTime, timeLen)) { + case "CALLBACK": + for (h in attrs) { + func[attrs[h]] = maxVal; + } + upd(); + return onFinish == "ud" ? true : onFinish(); + case "FOREVER": + stTime = time(); + break; + case "CONTINUE": + for (h in attrs) { + func[attrs[h]] = minVal; + } + break; + case "PING": + var buffer = minVal; + minVal = maxVal; + maxVal = buffer; + stTime = time(); + break; + default: + break; + } + for (i in attrs) { + func[attrs[i]] = lerp(minVal, maxVal, ease((time() - stTime) / timeLen)); + } + }); +} \ No newline at end of file diff --git a/code/main.js b/code/main.js index c76d0be..30a8f5b 100644 --- a/code/main.js +++ b/code/main.js @@ -1,4 +1,5 @@ import kaboom from "kaboom" +import { easings, tween, tweentypes } from "./easing.js" // initialize context kaboom({ @@ -14,6 +15,8 @@ load(new Promise((resolve, reject) => { // Music loadSound("tutorial", "sounds/Getting it Done.mp3"); //135 loadSound("faith", "sounds/The Friendly Faith Plate.mp3"); //120 + loadSound("sonic", "sounds/SonicInMidSim.wav"); //139 + loadSound("green", "sounds/GreenHill.wav"); //139 loadSound("gameover", "sounds/gameover.mp3"); // @@ -58,6 +61,7 @@ load(new Promise((resolve, reject) => { loadSound("hitsoundCarterRedacted", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundMarc", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundRedVelvety", "sounds/hitsoundRedVelvety.wav"); + loadSound("hitsoundSonicAndTails", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundMarkyMark", "sounds/burp.mp3"); loadSprite("JellyBeanPre", "sprites/previews/JellyBeanPre.png"); loadSprite("RedVelvetyPre", "sprites/previews/RedVelvetyPre.png"); @@ -65,6 +69,7 @@ load(new Promise((resolve, reject) => { loadSprite("CarterRedactedPre", "sprites/previews/CarterRedactedPre.png"); loadSprite("MarcPre", "sprites/previews/MarcPre.png"); loadSprite("FaithPlatePre", "sprites/previews/FaithPlatePre.png"); + loadSprite("SonicAndTailsPre", "sprites/previews/SonicAndTailsPre.png"); // // Cake/tutorial @@ -213,6 +218,138 @@ load(new Promise((resolve, reject) => { }, }) // + + // Sonic In Mid Sim/sonic + loadSprite("sonicBG0", "sprites/SonicBG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 6, + speed: 10, + loop: true + } + }, + }); + loadSprite("sonicFG0", "sprites/SonicFG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 0, + speed: 20 + } + }, + }); + loadSprite("SonicAndTailssonic0", "sprites/SonicMidSim.png", { + sliceX: 3, + sliceY: 4, + anims: { + idle: { + from: 0, + to: 6, + speed: 20 + }, + talk: { + from: 7, + to: 8, + speed: 20 + }, + miss: { + from: 9, + to: 11, + speed: 10 + }, + }, + }); + loadSprite("SonicAndTailssonic1", "sprites/TailsMidSim.png", { + sliceX: 3, + sliceY: 9, + anims: { + idle: { + from: 0, + to: 15, + speed: 20 + }, + talk: { + from: 16, + to: 23, + speed: 20 + }, + miss: { + from: 24, + to: 26, + speed: 10 + }, + }, + }); + loadSprite("greenBG0", "sprites/SonicBG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 6, + speed: 10, + loop: true + } + }, + }); + loadSprite("greenFG0", "sprites/SonicFG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 0, + speed: 20 + } + }, + }); + loadSprite("SonicAndTailsgreen0", "sprites/SonicMidSim.png", { + sliceX: 3, + sliceY: 4, + anims: { + idle: { + from: 0, + to: 6, + speed: 20 + }, + talk: { + from: 7, + to: 8, + speed: 20 + }, + miss: { + from: 9, + to: 11, + speed: 10 + }, + }, + }); + loadSprite("SonicAndTailsgreen1", "sprites/TailsMidSim.png", { + sliceX: 3, + sliceY: 9, + anims: { + idle: { + from: 0, + to: 15, + speed: 20 + }, + talk: { + from: 16, + to: 23, + speed: 20 + }, + miss: { + from: 24, + to: 26, + speed: 10 + }, + }, + }); resolve("All assets loaded."); })) @@ -229,6 +366,18 @@ var charts = { 120, "................................................................................................................................J...J...J...JJJJJ...J...J...J...J.J.J.J.J.J.J.J.JJJJJJJJJ.J.J.JJJ.J.J.J.J.J.JJJJJ.JJJ.J.J.J.J.J.J.JJJ.JJJ.J.J.J.JJJJJJJJJJJJJJJJJ.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.JJJ.JJJ.JJJ.JJJ.JJJ.J.J.J.J.JJJ.J.JJJJJJJJJJJJJ.J.J.J.JJJJJ.J.J.J.J.J.J.J.JJJJJ.J.J.J.J.JJ.J..J.JJJ.JJJ.J.J.JJJ.JJJ.J.J.J.JJJJJ.J.J.JJJ.JJJJJJJJJJJJJJJJJ.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.J.J.J.J.JJJ.JJJJ.JJ.J.J.J.JJ..J...J.J.J.J.J.J.JJJJJJJJJJJJJ.J.J.J.J.J.J.J.J.J.JJJJJ.J.J.J.J.J.J.J.J...J...J.J.J.J.JJJJJJJJJ.J.J.J.J.J.J.JJJ.J.JJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJJ.JJJJJJJJJJJJJJJ.JJJJJJJJJJJJJ..J.JJJ.JJJJJJJJJJ....J.J.JJJJJJJ.J.JJJ.JJJJJJJJJJJJJJJJJJJJJ.J.J.J.JJJ.JJJJJJJJJ....JJJJJJJJJ....JJJJJJJ.JJJJJJJ..JJJ..J.JJJJJJJJJJJJJJJ.JJJJJJJ..JJJ....JJJJJJJJJJJJJJJ.JJJJJJJ..JJJ....JJJJJJJJJJJJJJJ.JJJJJJJJ.JJJ..J.JJJJJJJ.J.JJJ.JJJJJJJJJJJJJ.JJJ.J...JJJ.J.JJJ.JJJJJJJJJJJJJ.JJJ.JJJ.JJJ.J.JJJJJJJJJJJJJJJJJJJJJ.JJJ.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.JJJ.JJJ.J.JJJ.JJJJJJJJJJJJJJJ...JJJJ........J...JJJJ........J...JJJJ........J...JJJJ........J.J.JJJJ........JJJ.JJJ.JJJ.J.JJJ.J.JJJJ........JJJ.JJJ.JJJ.JJJJJ.J.JJJJ........JJJ.JJJ.JJJ.J.JJJ.J.JJJJ........JJJ.JJJ.JJJ.JJJJJ.J.JJJJJ.......JJJ.J.............", 4 + ], + "sonic": [ + 2, + 139, + "..................................................J.J.J.J.J.JJ....J.J.J.J..J........JJJJ..J.JJ.J..................T.T.T.T.T.TT....T...T.T..T........TTT.T.T.TT.T..................J.J.J.J.J.J.J.J.J...J...........J.J.J.J.J.JJ..J.................T.T.T.T.T.T.T.T.T...T...........T.T.T.T.T.TT..T...............J.....J.....J.J.J.JJ....T.TT........J.J.J.J.J.J.J.J...J.........T.....T.....T.T.T.TT....J.JJ....DD.D.............................................................", + 1 + ], + "green": [ + 2, + 150, + "................J.....J.....J.....J.....J...J...J.....J.....J...................J.....J.....J...J.....J.....J...J.....J.................................T.T...T.T...T.T...T...........T.T.T...T.T...T.T...T.............T.T...T.T...T.T...T...........T.T.T...T.T...T.T...T.....T.......J.J...J.J...D.J.T.J.T...T.....J.J.J...J.J...D.J.T.J.T...T.......J.J...J.J...D.J.T.J.T...T.....J.J.J...J.J...D.J.T.J.D...D.....J.....J.....J.....J.T.D.T.J.....J.....J.....J.....J.T.D.T.J.....J.....J.....J.....J.T.D.T.J.T.T.T...T.T.T...T.T.T.T.T.T.T.........J.J...J.J...J.J...J...........J.J.J...J.J...J.J...J.............J.J...J.J...J.J...J...........J.J.J...J.J...J.J...J.....J.......T.T...T.T...D.T.J.T.J...J.....T.T.T...T.T...D.T.J.T.J...J.......T.T...T.T...D.T.J.T.J...J.....T.T.T...T.T...D.T.J.T.J.T.D.....T.....T.....T.....T.J.D.J.T.....T.....T.....T.....T.J.D.J.T.....T.....T.....T.....T.J.D.J.T.J.J.J...J.J.J...J.J.J.J.J.J.J................................................................................................................................................................", + 1 ] }; var songIdx = 0; @@ -275,18 +424,38 @@ scene("Game", (arr) => { "ui0", "ui1" ], "ui0"); - const player = add([ - sprite(char + song), - layer("JELLYBEAN"), - "dances", - (song == "faith" ? pos(0, 0) : pos((width() / 2) - 162, height() - 400)), - scale(chart[3]) - ]) + var player; + var player2; + if (song == "sonic" || song == "green") { + player = add([ + sprite(char + song + "0"), + layer("JELLYBEAN"), + "dances", + pos(20, 108), + scale(chart[3]) + ]) + player2 = add([ + sprite(char + song + "1"), + layer("JELLYBEAN"), + "dances", + pos(224, 20), + scale(chart[3]) + ]) + } else { + player = add([ + sprite(char + song), + layer("JELLYBEAN"), + "dances", + (song == "faith" ? pos(0, 0) : pos((width() / 2) - 162, height() - 400)), + scale(chart[3]) + ]) + } const bg = add([ sprite(song + "BG0"), layer("bg"), scale(chart[3]) ]) + if(song == "sonic" || song == "green") bg.play("idle"); const fg = add([ sprite(song + "FG0"), (song == "faith" ? layer("bg") : layer("fg")), @@ -342,13 +511,13 @@ scene("Game", (arr) => { every("note", (j) => { j.pos.x = lerp(width(), strumLine, (underlay.time() - j.created) / chart[0]); if(autoplay) { - if(j.pos.x <= strumLine) { + if(j.pos.x <= strumLine && !j.empty) { play(hitsound); player.play("talk"); destroy(j); } } else { - if(j.pos.x <= strumLine - 20) { + if(j.pos.x <= strumLine - 20 && !j.empty) { score -= 200; destroy(j); play("explode"); @@ -358,6 +527,12 @@ scene("Game", (arr) => { health -= 0.1; } } + if(j.pos.x >= strumLine - 20 && j.empty) { + if(j.pos.x <= strumLine) { + destroy(j); //Destroys note. No score. + player2?.play("talk"); + } + } }); } }) @@ -476,36 +651,104 @@ scene("Game", (arr) => { } ]); break; + case "T": + add([ + rect(0, 50), + pos(width(), 20), + ("note" + prevStep), + "note", + "empty", + { + created: underlay.time(), + empty: true, + type: chart[2][Math.floor(prevStep)] + } + ]); + break; + case "D": + add([ + rect(10, 50), + pos(width(), 20), + chart[2][Math.floor(prevStep)] == "F" ? color(168, 56, 50) : color(232, 3, 252), + ("note" + prevStep), + "note", + { + created: underlay.time(), + type: chart[2][Math.floor(prevStep)] + } + ]); + add([ + rect(0, 50), + pos(width(), 20), + ("note" + prevStep), + "note", + "empty", + { + created: underlay.time(), + empty: true, + type: chart[2][Math.floor(prevStep)] + } + ]); + break; } } function judgeHitsLol() { var iv = false; every("note", (j) => { if (!iv) { - if(j.pos.x >= strumLine - 20) { - if(j.pos.x <= strumLine + 22) { - iv = true; - destroy(j); //Destroys note. No score. - noteClick.play("click"); - combo += 1; + if (!j.empty) { + var str = "No Rating"; + var theColor = WHITE; + if(j.pos.x >= strumLine - 20) { + if(j.pos.x <= strumLine + 22) { + iv = true; + destroy(j); //Destroys note. No score. + noteClick.play("click"); + combo += 1; + str = "MID"; + theColor = RED; + } + if(j.pos.x <= strumLine + 12) { + play(hitsound); //Plays sound! + player.play("talk"); + score += 20; + health += 0.01; + str = "Perfect!"; + theColor = MAGENTA; + } + if(j.pos.x <= strumLine) { + score += 50; + health += 0.02; + str = "Marvelous!"; + theColor = YELLOW; + } + if(j.pos.x <= strumLine - 3) { + score += 50; + health -= 0.01; + str = "Perfect!"; + theColor = MAGENTA; + } + if(j.pos.x <= strumLine - 8) { + score -= 100; + health -= 0.02; + str = "Overshot"; + theColor = CYAN; + } } - if(j.pos.x <= strumLine + 12) { - play(hitsound); //Plays sound! - player.play("talk"); - score += 20; - health += 0.01; - } - if(j.pos.x <= strumLine) { - score += 50; - health += 0.02; - } - if(j.pos.x <= strumLine - 3) { - score += 50; - health -= 0.01; - } - if(j.pos.x <= strumLine - 8) { - score -= 100; - health -= 0.02; + if (str != "No Rating") { + var origpos = strumLine - 16; + var ratingtxt = add([ + text(str, { + size: 30, // 48 pixels tall + }), + pos(origpos, 48), + color(theColor), + origin("right") + ]); + tween(ratingtxt.pos, ["y"], 5, 48, 300, easings.easeOutSine, tweentypes.NORMAL); + tween(ratingtxt, ["opacity"], 5, 1, 0, easings.easeOutSine, tweentypes.NORMAL, function() { + destroy(ratingtxt); + }); } } } @@ -523,7 +766,15 @@ scene("Game", (arr) => { scene("Help", () => { var songs = [ "tutorial", - "faith" + "faith", + "green", + "sonic" + ]; + var names = [ + "Tutorial", + "Friendly Faith Plate", + "Green Hill Zone", + "Emerald Hill Zone" ] var chars = { tutorial: [ @@ -535,6 +786,12 @@ scene("Help", () => { ], faith: [ "FaithPlate" + ], + green: [ + "SonicAndTails" + ], + sonic: [ + "SonicAndTails" ] } const bg = add([ @@ -586,7 +843,7 @@ scene("Help", () => { origin: "top" }); drawText({ - text: "SONG ("+ (songIdx + 1) +"/"+ songs.length +"): " + songs[songIdx].toUpperCase(), + text: "SONG ("+ (songIdx + 1) +"/"+ songs.length +"): " + names[songIdx].toUpperCase(), size: 30, pos: vec2(width() / 2, 160), origin: "top" @@ -663,15 +920,15 @@ scene("Help", () => { onKeyPress("left", () => {changeIdx(-1)}); onKeyPress("right", () => {changeIdx(1)}); - onKeyPress("down", () => {changeSongIdx(-1)}); - onKeyPress("up", () => {changeSongIdx(1)}); + onKeyPress("down", () => {changeSongIdx(1)}); + onKeyPress("up", () => {changeSongIdx(-1)}); onKeyPress("`", () => {go("Chart", songs[songIdx])}); onClick("LeftText", () => {changeIdx(-1)}); onClick("RightText", () => {changeIdx(1)}); - onClick("DownText", () => {changeSongIdx(-1)}); - onClick("UpText", () => {changeSongIdx(1)}); + onClick("DownText", () => {changeSongIdx(1)}); + onClick("UpText", () => {changeSongIdx(-1)}); onKeyPress("space", () => {go("Game", [songs[songIdx], chars[songs[songIdx]][idx]]);/*losemus.stop();*/}); - onClick("TEXT TEXT", () => {go("Game", ["tutorial", chars[songs[songIdx]][idx]]);/*losemus.stop();*/}); + onClick("TEXT TEXT", () => {go("Game", [songs[songIdx], chars[songs[songIdx]][idx]]);/*losemus.stop();*/}); }); scene("Title", () => { @@ -786,9 +1043,9 @@ scene("Chart", (song) => { ]); var theP = add([ pos(width() * 0.6, height() * 0.8), - rect(60, 60), + rect(60, 30), text("P", { - size: 48, // 48 pixels tall + size: 24, // 48 pixels tall width: 60 }), color(255, 255, 255), @@ -797,6 +1054,19 @@ scene("Chart", (song) => { area(), "theP" ]); + var theD = add([ + pos(width() * 0.6, height() * 0.8 + 30), + rect(60, 30), + text("D", { + size: 24, // 48 pixels tall + width: 60 + }), + color(255, 255, 255), + outline(4, WHITE), + origin("center"), + area(), + "theD" + ]); var consoleButton = add([ pos(width() * 0.8, height() * 0.8), rect(60, 60), @@ -812,6 +1082,7 @@ scene("Chart", (song) => { onClick("theEmpty", (o) => {tool = "."}) onClick("theJ", (o) => {tool = "J"}) onClick("theP", (o) => {tool = "P"}) + onClick("theD", (o) => {tool = "D"}) onClick("consoleButton", (o) => {console.log(tempChart.join(""));}) onUpdate(() => { curBeat = Math.floor(((music.time() * 1000) / crochet) * 10) / 10; diff --git a/dist/game.js b/dist/game.js index 2d9b8d8..4c9ccee 100644 --- a/dist/game.js +++ b/dist/game.js @@ -9,19 +9,19 @@ var rr = Object.getOwnPropertySymbols; var Ji = Object.prototype.hasOwnProperty; var Qi = Object.prototype.propertyIsEnumerable; - var Nt = /* @__PURE__ */ __name((i, t, l) => t in i ? ir(i, t, { enumerable: true, configurable: true, writable: true, value: l }) : i[t] = l, "Nt"); - var P = /* @__PURE__ */ __name((i, t) => { + var Nt = /* @__PURE__ */ __name((i2, t, l) => t in i2 ? ir(i2, t, { enumerable: true, configurable: true, writable: true, value: l }) : i2[t] = l, "Nt"); + var P = /* @__PURE__ */ __name((i2, t) => { for (var l in t || (t = {})) - Ji.call(t, l) && Nt(i, l, t[l]); + Ji.call(t, l) && Nt(i2, l, t[l]); if (rr) for (var l of rr(t)) - Qi.call(t, l) && Nt(i, l, t[l]); - return i; + Qi.call(t, l) && Nt(i2, l, t[l]); + return i2; }, "P"); - var D = /* @__PURE__ */ __name((i, t) => Hi(i, zi(t)), "D"); - var a = /* @__PURE__ */ __name((i, t) => ir(i, "name", { value: t, configurable: true }), "a"); - var b = /* @__PURE__ */ __name((i, t, l) => (Nt(i, typeof t != "symbol" ? t + "" : t, l), l), "b"); - var sr = /* @__PURE__ */ __name((i, t, l) => new Promise((w, U) => { + var D = /* @__PURE__ */ __name((i2, t) => Hi(i2, zi(t)), "D"); + var a = /* @__PURE__ */ __name((i2, t) => ir(i2, "name", { value: t, configurable: true }), "a"); + var b = /* @__PURE__ */ __name((i2, t, l) => (Nt(i2, typeof t != "symbol" ? t + "" : t, l), l), "b"); + var sr = /* @__PURE__ */ __name((i2, t, l) => new Promise((w, U) => { var p = /* @__PURE__ */ __name((q) => { try { A(l.next(q)); @@ -35,14 +35,14 @@ U(V); } }, "S"), A = /* @__PURE__ */ __name((q) => q.done ? w(q.value) : Promise.resolve(q.value).then(p, S), "A"); - A((l = l.apply(i, t)).next()); + A((l = l.apply(i2, t)).next()); }), "sr"); var or = (() => { - for (var i = new Uint8Array(128), t = 0; t < 64; t++) - i[t < 26 ? t + 65 : t < 52 ? t + 71 : t < 62 ? t - 4 : t * 4 - 205] = t; + for (var i2 = new Uint8Array(128), t = 0; t < 64; t++) + i2[t < 26 ? t + 65 : t < 52 ? t + 71 : t < 62 ? t - 4 : t * 4 - 205] = t; return (l) => { for (var w = l.length, U = new Uint8Array((w - (l[w - 1] == "=") - (l[w - 2] == "=")) * 3 / 4 | 0), p = 0, S = 0; p < w; ) { - var A = i[l.charCodeAt(p++)], q = i[l.charCodeAt(p++)], V = i[l.charCodeAt(p++)], j = i[l.charCodeAt(p++)]; + var A = i2[l.charCodeAt(p++)], q = i2[l.charCodeAt(p++)], V = i2[l.charCodeAt(p++)], j = i2[l.charCodeAt(p++)]; U[S++] = A << 2 | q >> 4, U[S++] = q << 4 | V >> 2, U[S++] = V << 6 | j; } return U; @@ -64,80 +64,80 @@ } }, "$"); a($, "IDList"); - function jt(i, t) { - let l = typeof i, w = typeof t; + function jt(i2, t) { + let l = typeof i2, w = typeof t; if (l !== w) return false; if (l === "object" && w === "object") { - let U = Object.keys(i), p = Object.keys(t); + let U = Object.keys(i2), p = Object.keys(t); if (U.length !== p.length) return false; for (let S of U) { - let A = i[S], q = t[S]; + let A = i2[S], q = t[S]; if (!(typeof A == "function" && typeof q == "function") && !jt(A, q)) return false; } return true; } - return i === t; + return i2 === t; } __name(jt, "jt"); a(jt, "deepEq"); - function Yt(i, t) { + function Yt(i2, t) { let l = document.createElement("a"); - document.body.appendChild(l), l.setAttribute("style", "display: none"), l.href = i, l.download = t, l.click(), document.body.removeChild(l); + document.body.appendChild(l), l.setAttribute("style", "display: none"), l.href = i2, l.download = t, l.click(), document.body.removeChild(l); } __name(Yt, "Yt"); a(Yt, "downloadURL"); - function ur(i, t) { - let l = URL.createObjectURL(i); + function ur(i2, t) { + let l = URL.createObjectURL(i2); Yt(l, t), URL.revokeObjectURL(l); } __name(ur, "ur"); a(ur, "downloadBlob"); - function cr(i) { - return i.match(/^data:\w+\/\w+;base64,.+/); + function cr(i2) { + return i2.match(/^data:\w+\/\w+;base64,.+/); } __name(cr, "cr"); a(cr, "isDataURL"); var lr = (() => { - let i = 0; - return () => i++; + let i2 = 0; + return () => i2++; })(); var ar = /* @__PURE__ */ new Set(); - function B(i, t) { - ar.has(i) || (ar.add(i), console.warn(`${i} is deprecated. Use ${t} instead.`)); + function B(i2, t) { + ar.has(i2) || (ar.add(i2), console.warn(`${i2} is deprecated. Use ${t} instead.`)); } __name(B, "B"); a(B, "deprecateMsg"); - var T = a((i, t, l) => (...w) => (B(i, t), l(...w)), "deprecate"); - function he(i) { - return i * Math.PI / 180; + var T = a((i2, t, l) => (...w) => (B(i2, t), l(...w)), "deprecate"); + function he(i2) { + return i2 * Math.PI / 180; } __name(he, "he"); a(he, "deg2rad"); - function Xt(i) { - return i * 180 / Math.PI; + function Xt(i2) { + return i2 * 180 / Math.PI; } __name(Xt, "Xt"); a(Xt, "rad2deg"); - function z(i, t, l) { - return t > l ? z(i, l, t) : Math.min(Math.max(i, t), l); + function z(i2, t, l) { + return t > l ? z(i2, l, t) : Math.min(Math.max(i2, t), l); } __name(z, "z"); a(z, "clamp"); - function Ve(i, t, l) { - return i + (t - i) * l; + function Ve(i2, t, l) { + return i2 + (t - i2) * l; } __name(Ve, "Ve"); a(Ve, "lerp"); - function dt(i, t, l, w, U) { - return w + (i - t) / (l - t) * (U - w); + function dt(i2, t, l, w, U) { + return w + (i2 - t) / (l - t) * (U - w); } __name(dt, "dt"); a(dt, "map"); - function dr(i, t, l, w, U) { - return z(dt(i, t, l, w, U), w, U); + function dr(i2, t, l, w, U) { + return z(dt(i2, t, l, w, U), w, U); } __name(dr, "dr"); a(dr, "mapc"); @@ -204,14 +204,14 @@ }, "N"); var L = N; a(L, "Vec2"), b(L, "LEFT", new N(-1, 0)), b(L, "RIGHT", new N(1, 0)), b(L, "UP", new N(0, -1)), b(L, "DOWN", new N(0, 1)); - function f(...i) { - if (i.length === 1) { - if (i[0] instanceof L) - return f(i[0].x, i[0].y); - if (Array.isArray(i[0]) && i[0].length === 2) - return f.apply(null, i[0]); + function f(...i2) { + if (i2.length === 1) { + if (i2[0] instanceof L) + return f(i2[0].x, i2[0].y); + if (Array.isArray(i2[0]) && i2[0].length === 2) + return f.apply(null, i2[0]); } - return new L(...i); + return new L(...i2); } __name(f, "f"); a(f, "vec2"); @@ -227,7 +227,7 @@ } }, "Fe"); a(Fe, "Vec3"); - var de = a((i, t, l) => new Fe(i, t, l), "vec3"); + var de = a((i2, t, l) => new Fe(i2, t, l), "vec3"); var ue = /* @__PURE__ */ __name(class { constructor(t, l, w) { b(this, "r", 255); @@ -271,20 +271,20 @@ }, "ue"); var v = ue; a(v, "Color"), b(v, "RED", E(255, 0, 0)), b(v, "GREEN", E(0, 255, 0)), b(v, "BLUE", E(0, 0, 255)), b(v, "YELLOW", E(255, 255, 0)), b(v, "MAGENTA", E(255, 0, 255)), b(v, "CYAN", E(0, 255, 255)), b(v, "WHITE", E(255, 255, 255)), b(v, "BLACK", E(0, 0, 0)); - function E(...i) { - if (i.length === 0) + function E(...i2) { + if (i2.length === 0) return new v(255, 255, 255); - if (i.length === 1) { - if (i[0] instanceof v) - return i[0].clone(); - if (Array.isArray(i[0]) && i[0].length === 3) - return v.fromArray(i[0]); + if (i2.length === 1) { + if (i2[0] instanceof v) + return i2[0].clone(); + if (Array.isArray(i2[0]) && i2[0].length === 3) + return v.fromArray(i2[0]); } - return new v(...i); + return new v(...i2); } __name(E, "E"); a(E, "rgb"); - var fr = a((i, t, l) => v.fromHSL(i, t, l), "hsl2rgb"); + var fr = a((i2, t, l) => v.fromHSL(i2, t, l), "hsl2rgb"); var k = /* @__PURE__ */ __name(class { constructor(t, l, w, U) { b(this, "x", 0); @@ -307,8 +307,8 @@ } }, "k"); a(k, "Quad"); - function pr(i, t, l, w) { - return new k(i, t, l, w); + function pr(i2, t, l, w) { + return new k(i2, t, l, w); } __name(pr, "pr"); a(pr, "quad"); @@ -381,8 +381,8 @@ } }, "R"); a(R, "Mat4"); - function Kt(i, t, l, w = Math.sin) { - return i + (w(l) + 1) / 2 * (t - i); + function Kt(i2, t, l, w = Math.sin) { + return i2 + (w(l) + 1) / 2 * (t - i2); } __name(Kt, "Kt"); a(Kt, "wave"); @@ -416,124 +416,124 @@ }, "be"); a(be, "RNG"); var $t = new be(Date.now()); - function mr(i) { - return B("rng()", "new RNG()"), new be(i); + function mr(i2) { + return B("rng()", "new RNG()"), new be(i2); } __name(mr, "mr"); a(mr, "rng"); - function wr(i) { - return i != null && ($t.seed = i), $t.seed; + function wr(i2) { + return i2 != null && ($t.seed = i2), $t.seed; } __name(wr, "wr"); a(wr, "randSeed"); - function Ge(...i) { - return $t.gen(...i); + function Ge(...i2) { + return $t.gen(...i2); } __name(Ge, "Ge"); a(Ge, "rand"); - function Ht(...i) { - return Math.floor(Ge(...i)); + function Ht(...i2) { + return Math.floor(Ge(...i2)); } __name(Ht, "Ht"); a(Ht, "randi"); - function gr(i) { - return Ge() <= i; + function gr(i2) { + return Ge() <= i2; } __name(gr, "gr"); a(gr, "chance"); - function Ur(i) { - return i[Ht(i.length)]; + function Ur(i2) { + return i2[Ht(i2.length)]; } __name(Ur, "Ur"); a(Ur, "choose"); - function yr(i, t) { - return i.p2.x >= t.p1.x && i.p1.x <= t.p2.x && i.p2.y >= t.p1.y && i.p1.y <= t.p2.y; + function yr(i2, t) { + return i2.p2.x >= t.p1.x && i2.p1.x <= t.p2.x && i2.p2.y >= t.p1.y && i2.p1.y <= t.p2.y; } __name(yr, "yr"); a(yr, "testRectRect2"); - function zt(i, t) { - return i.p2.x > t.p1.x && i.p1.x < t.p2.x && i.p2.y > t.p1.y && i.p1.y < t.p2.y; + function zt(i2, t) { + return i2.p2.x > t.p1.x && i2.p1.x < t.p2.x && i2.p2.y > t.p1.y && i2.p1.y < t.p2.y; } __name(zt, "zt"); a(zt, "testRectRect"); - function Jt(i, t) { - if (i.p1.x === i.p2.x && i.p1.y === i.p2.y || t.p1.x === t.p2.x && t.p1.y === t.p2.y) + function Jt(i2, t) { + if (i2.p1.x === i2.p2.x && i2.p1.y === i2.p2.y || t.p1.x === t.p2.x && t.p1.y === t.p2.y) return null; - let l = (t.p2.y - t.p1.y) * (i.p2.x - i.p1.x) - (t.p2.x - t.p1.x) * (i.p2.y - i.p1.y); + let l = (t.p2.y - t.p1.y) * (i2.p2.x - i2.p1.x) - (t.p2.x - t.p1.x) * (i2.p2.y - i2.p1.y); if (l === 0) return null; - let w = ((t.p2.x - t.p1.x) * (i.p1.y - t.p1.y) - (t.p2.y - t.p1.y) * (i.p1.x - t.p1.x)) / l, U = ((i.p2.x - i.p1.x) * (i.p1.y - t.p1.y) - (i.p2.y - i.p1.y) * (i.p1.x - t.p1.x)) / l; + let w = ((t.p2.x - t.p1.x) * (i2.p1.y - t.p1.y) - (t.p2.y - t.p1.y) * (i2.p1.x - t.p1.x)) / l, U = ((i2.p2.x - i2.p1.x) * (i2.p1.y - t.p1.y) - (i2.p2.y - i2.p1.y) * (i2.p1.x - t.p1.x)) / l; return w < 0 || w > 1 || U < 0 || U > 1 ? null : w; } __name(Jt, "Jt"); a(Jt, "testLineLineT"); - function ce(i, t) { - let l = Jt(i, t); - return l ? f(i.p1.x + l * (i.p2.x - i.p1.x), i.p1.y + l * (i.p2.y - i.p1.y)) : null; + function ce(i2, t) { + let l = Jt(i2, t); + return l ? f(i2.p1.x + l * (i2.p2.x - i2.p1.x), i2.p1.y + l * (i2.p2.y - i2.p1.y)) : null; } __name(ce, "ce"); a(ce, "testLineLine"); - function ft(i, t) { - return ae(i, t.p1) || ae(i, t.p2) ? true : !!ce(t, new le(i.p1, f(i.p2.x, i.p1.y))) || !!ce(t, new le(f(i.p2.x, i.p1.y), i.p2)) || !!ce(t, new le(i.p2, f(i.p1.x, i.p2.y))) || !!ce(t, new le(f(i.p1.x, i.p2.y), i.p1)); + function ft(i2, t) { + return ae(i2, t.p1) || ae(i2, t.p2) ? true : !!ce(t, new le(i2.p1, f(i2.p2.x, i2.p1.y))) || !!ce(t, new le(f(i2.p2.x, i2.p1.y), i2.p2)) || !!ce(t, new le(i2.p2, f(i2.p1.x, i2.p2.y))) || !!ce(t, new le(f(i2.p1.x, i2.p2.y), i2.p1)); } __name(ft, "ft"); a(ft, "testRectLine"); - function ae(i, t) { - return t.x > i.p1.x && t.x < i.p2.x && t.y > i.p1.y && t.y < i.p2.y; + function ae(i2, t) { + return t.x > i2.p1.x && t.x < i2.p2.x && t.y > i2.p1.y && t.y < i2.p2.y; } __name(ae, "ae"); a(ae, "testRectPoint"); - function br(i, t) { - let l = Math.max(i.p1.x, Math.min(t.center.x, i.p2.x)), w = Math.max(i.p1.y, Math.min(t.center.y, i.p2.y)); + function br(i2, t) { + let l = Math.max(i2.p1.x, Math.min(t.center.x, i2.p2.x)), w = Math.max(i2.p1.y, Math.min(t.center.y, i2.p2.y)); return f(l, w).dist(t.center) <= t.radius; } __name(br, "br"); a(br, "testRectCircle"); - function pt(i, t) { - return wt(t, [i.p1, f(i.p2.x, i.p1.y), i.p2, f(i.p1.x, i.p2.y)]); + function pt(i2, t) { + return wt(t, [i2.p1, f(i2.p2.x, i2.p1.y), i2.p2, f(i2.p1.x, i2.p2.y)]); } __name(pt, "pt"); a(pt, "testRectPolygon"); - function xr(i, t) { + function xr(i2, t) { return false; } __name(xr, "xr"); a(xr, "testLinePoint"); - function vr(i, t) { + function vr(i2, t) { return false; } __name(vr, "vr"); a(vr, "testLineCircle"); - function _e(i, t) { - if (xe(t, i.p1) || xe(t, i.p2)) + function _e(i2, t) { + if (xe(t, i2.p1) || xe(t, i2.p2)) return true; for (let l = 0; l < t.length; l++) { let w = t[l], U = t[(l + 1) % t.length]; - if (ce(i, { p1: w, p2: U })) + if (ce(i2, { p1: w, p2: U })) return true; } return false; } __name(_e, "_e"); a(_e, "testLinePolygon"); - function mt(i, t) { - return i.center.dist(t) < i.radius; + function mt(i2, t) { + return i2.center.dist(t) < i2.radius; } __name(mt, "mt"); a(mt, "testCirclePoint"); - function Qt(i, t) { - return i.center.dist(t.center) < i.radius + t.radius; + function Qt(i2, t) { + return i2.center.dist(t.center) < i2.radius + t.radius; } __name(Qt, "Qt"); a(Qt, "testCircleCircle"); - function Er(i, t) { + function Er(i2, t) { return false; } __name(Er, "Er"); a(Er, "testCirclePolygon"); - function wt(i, t) { - for (let l = 0; l < i.length; l++) { - let w = { p1: i[l], p2: i[(l + 1) % i.length] }; + function wt(i2, t) { + for (let l = 0; l < i2.length; l++) { + let w = { p1: i2[l], p2: i2[(l + 1) % i2.length] }; if (_e(w, t)) return true; } @@ -541,123 +541,123 @@ } __name(wt, "wt"); a(wt, "testPolygonPolygon"); - function xe(i, t) { + function xe(i2, t) { let l = false; - for (let w = 0, U = i.length - 1; w < i.length; U = w++) - i[w].y > t.y != i[U].y > t.y && t.x < (i[U].x - i[w].x) * (t.y - i[w].y) / (i[U].y - i[w].y) + i[w].x && (l = !l); + for (let w = 0, U = i2.length - 1; w < i2.length; U = w++) + i2[w].y > t.y != i2[U].y > t.y && t.x < (i2[U].x - i2[w].x) * (t.y - i2[w].y) / (i2[U].y - i2[w].y) + i2[w].x && (l = !l); return l; } __name(xe, "xe"); a(xe, "testPolygonPoint"); - function ts(i, t) { - return i.eq(t); + function ts(i2, t) { + return i2.eq(t); } __name(ts, "ts"); a(ts, "testPointPoint"); - function gt(i, t) { - switch (i.shape) { + function gt(i2, t) { + switch (i2.shape) { case "rect": - return zt(t, i); + return zt(t, i2); case "line": - return ft(t, i); + return ft(t, i2); case "circle": - return br(t, i); + return br(t, i2); case "polygon": - return pt(t, i.pts); + return pt(t, i2.pts); case "point": - return ae(t, i.pt); + return ae(t, i2.pt); } - throw new Error(`Unknown area shape: ${i.shape}`); + throw new Error(`Unknown area shape: ${i2.shape}`); } __name(gt, "gt"); a(gt, "testAreaRect"); - function Zt(i, t) { - switch (i.shape) { + function Zt(i2, t) { + switch (i2.shape) { case "rect": - return ft(i, t); + return ft(i2, t); case "line": - return Boolean(ce(i, t)); + return Boolean(ce(i2, t)); case "circle": - return vr(t, i); + return vr(t, i2); case "polygon": - return _e(t, i.pts); + return _e(t, i2.pts); case "point": - return xr(t, i.pt); + return xr(t, i2.pt); } - throw new Error(`Unknown area shape: ${i.shape}`); + throw new Error(`Unknown area shape: ${i2.shape}`); } __name(Zt, "Zt"); a(Zt, "testAreaLine"); - function en(i, t) { - switch (i.shape) { + function en(i2, t) { + switch (i2.shape) { case "rect": - return br(i, t); + return br(i2, t); case "line": - return vr(i, t); + return vr(i2, t); case "circle": - return Qt(i, t); + return Qt(i2, t); case "polygon": - return Er(t, i.pts); + return Er(t, i2.pts); case "point": - return mt(t, i.pt); + return mt(t, i2.pt); } - throw new Error(`Unknown area shape: ${i.shape}`); + throw new Error(`Unknown area shape: ${i2.shape}`); } __name(en, "en"); a(en, "testAreaCircle"); - function tn(i, t) { - switch (i.shape) { + function tn(i2, t) { + switch (i2.shape) { case "rect": - return pt(i, t); + return pt(i2, t); case "line": - return _e(i, t); + return _e(i2, t); case "circle": - return Er(i, t); + return Er(i2, t); case "polygon": - return wt(t, i.pts); + return wt(t, i2.pts); case "point": - return xe(t, i.pt); + return xe(t, i2.pt); } - throw new Error(`Unknown area shape: ${i.shape}`); + throw new Error(`Unknown area shape: ${i2.shape}`); } __name(tn, "tn"); a(tn, "testAreaPolygon"); - function Ut(i, t) { - switch (i.shape) { + function Ut(i2, t) { + switch (i2.shape) { case "rect": - return ae(i, t); + return ae(i2, t); case "line": - return xr(i, t); + return xr(i2, t); case "circle": - return mt(i, t); + return mt(i2, t); case "polygon": - return xe(i.pts, t); + return xe(i2.pts, t); case "point": - return ts(i.pt, t); + return ts(i2.pt, t); } - throw new Error(`Unknown area shape: ${i.shape}`); + throw new Error(`Unknown area shape: ${i2.shape}`); } __name(Ut, "Ut"); a(Ut, "testAreaPoint"); - function nn(i, t) { + function nn(i2, t) { switch (t.shape) { case "rect": - return gt(i, t); + return gt(i2, t); case "line": - return Zt(i, t); + return Zt(i2, t); case "circle": - return en(i, t); + return en(i2, t); case "polygon": - return tn(i, t.pts); + return tn(i2, t.pts); case "point": - return Ut(i, t.pt); + return Ut(i2, t.pt); } throw new Error(`Unknown area shape: ${t.shape}`); } __name(nn, "nn"); a(nn, "testAreaArea"); - function yt(i, t) { - return { p1: f(i.p1.x - t.p2.x, i.p1.y - t.p2.y), p2: f(i.p2.x - t.p1.x, i.p2.y - t.p1.y) }; + function yt(i2, t) { + return { p1: f(i2.p1.x - t.p2.x, i2.p1.y - t.p2.y), p2: f(i2.p2.x - t.p1.x, i2.p2.y - t.p1.y) }; } __name(yt, "yt"); a(yt, "minkDiff"); @@ -795,13 +795,13 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { `; var ys = /* @__PURE__ */ new Set(["id", "require"]); var bs = /* @__PURE__ */ new Set(["add", "load", "update", "draw", "destroy", "inspect"]); - function kr(i) { - return i === "pressed" || i === "rpressed" ? "down" : i === "released" ? "up" : i; + function kr(i2) { + return i2 === "pressed" || i2 === "rpressed" ? "down" : i2 === "released" ? "up" : i2; } __name(kr, "kr"); a(kr, "processButtonState"); - function xs(i) { - i.requestFullscreen ? i.requestFullscreen() : i.webkitRequestFullscreen && i.webkitRequestFullscreen(); + function xs(i2) { + i2.requestFullscreen ? i2.requestFullscreen() : i2.webkitRequestFullscreen && i2.webkitRequestFullscreen(); } __name(xs, "xs"); a(xs, "enterFullscreen"); @@ -815,8 +815,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } __name(Es, "Es"); a(Es, "getFullscreenElement"); - function Ye(i) { - switch (i) { + function Ye(i2) { + switch (i2) { case "topleft": return f(-1, -1); case "top": @@ -836,7 +836,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { case "botright": return f(1, 1); default: - return i; + return i2; } } __name(Ye, "Ye"); @@ -846,26 +846,26 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } __name(Gr, "Gr"); a(Gr, "createEmptyAudioBuffer"); - var no = a((i = {}) => { + var no = a((i2 = {}) => { let t = (() => { var s, o, d; - let e = (s = i.root) != null ? s : document.body; + let e = (s = i2.root) != null ? s : document.body; e === document.body && (document.body.style.width = "100%", document.body.style.height = "100%", document.body.style.margin = "0px", document.documentElement.style.width = "100%", document.documentElement.style.height = "100%"); - let n = (o = i.canvas) != null ? o : (() => { - let h = document.createElement("canvas"); - return e.appendChild(h), h; - })(), r = (d = i.scale) != null ? d : 1; - i.width && i.height && !i.stretch && !i.letterbox ? (n.width = i.width * r, n.height = i.height * r) : (n.width = n.parentElement.offsetWidth, n.height = n.parentElement.offsetHeight); + let n = (o = i2.canvas) != null ? o : (() => { + let h2 = document.createElement("canvas"); + return e.appendChild(h2), h2; + })(), r = (d = i2.scale) != null ? d : 1; + i2.width && i2.height && !i2.stretch && !i2.letterbox ? (n.width = i2.width * r, n.height = i2.height * r) : (n.width = n.parentElement.offsetWidth, n.height = n.parentElement.offsetHeight); let u = ["outline: none", "cursor: default"]; - i.crisp && (u.push("image-rendering: pixelated"), u.push("image-rendering: crisp-edges")), n.style = u.join(";"), n.setAttribute("tabindex", "0"); + i2.crisp && (u.push("image-rendering: pixelated"), u.push("image-rendering: crisp-edges")), n.style = u.join(";"), n.setAttribute("tabindex", "0"); let c = n.getContext("webgl", { antialias: true, depth: true, stencil: true, alpha: true, preserveDrawingBuffer: true }); return { canvas: n, scale: r, gl: c, keyStates: {}, mouseStates: {}, charInputted: [], isMouseMoved: false, isKeyPressed: false, isKeyPressedRepeat: false, isKeyReleased: false, mousePos: f(0, 0), mouseDeltaPos: f(0, 0), time: 0, realTime: 0, skipTime: false, dt: 0, numFrames: 0, isTouch: "ontouchstart" in window || navigator.maxTouchPoints > 0, loopID: null, stopped: false, paused: false, fpsCounter: new Be(), loaded: false }; })(), l = (() => { var d; - let e = t.gl, n = cn(sn, on), r = Ke(new ImageData(new Uint8ClampedArray([255, 255, 255, 255]), 1, 1)), u = (d = i.background) != null ? d : E(0, 0, 0); - if (i.background) { - let h = v.fromArray(i.background); - e.clearColor(h.r / 255, h.g / 255, h.b / 255, 1); + let e = t.gl, n = cn(sn, on), r = Ke(new ImageData(new Uint8ClampedArray([255, 255, 255, 255]), 1, 1)), u = (d = i2.background) != null ? d : E(0, 0, 0); + if (i2.background) { + let h2 = v.fromArray(i2.background); + e.clearColor(h2.r / 255, h2.g / 255, h2.b / 255, 1); } e.enable(e.BLEND), e.enable(e.SCISSOR_TEST), e.blendFuncSeparate(e.SRC_ALPHA, e.ONE_MINUS_SRC_ALPHA, e.ONE, e.ONE_MINUS_SRC_ALPHA); let c = e.createBuffer(); @@ -873,7 +873,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let s = e.createBuffer(); e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, s), e.bufferData(e.ELEMENT_ARRAY_BUFFER, bt * 2, e.DYNAMIC_DRAW), e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, null); let o = Ke(new ImageData(new Uint8ClampedArray([128, 128, 128, 255, 190, 190, 190, 255, 190, 190, 190, 255, 128, 128, 128, 255]), 2, 2), { wrap: "repeat", filter: "nearest" }); - return { drawCalls: 0, lastDrawCalls: 0, defShader: n, curShader: n, defTex: r, curTex: r, curUniform: {}, vbuf: c, ibuf: s, vqueue: [], iqueue: [], transform: new R(), transformStack: [], bgTex: o, width: i.width, height: i.height, viewport: { x: 0, y: 0, width: e.drawingBufferWidth, height: e.drawingBufferHeight } }; + return { drawCalls: 0, lastDrawCalls: 0, defShader: n, curShader: n, defTex: r, curTex: r, curUniform: {}, vbuf: c, ibuf: s, vqueue: [], iqueue: [], transform: new R(), transformStack: [], bgTex: o, width: i2.width, height: i2.height, viewport: { x: 0, y: 0, width: e.drawingBufferWidth, height: e.drawingBufferHeight } }; })(); Un(); let w = (() => { @@ -963,10 +963,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { __name(vt, "vt"); a(vt, "getShader"); function ve(e = 1, n = 1, r = 0, u = 0, c = 1, s = 1) { - let o = [], d = c / e, h = s / n; + let o = [], d = c / e, h2 = s / n; for (let m = 0; m < n; m++) for (let g = 0; g < e; g++) - o.push(new k(r + g * d, u + m * h, d, h)); + o.push(new k(r + g * d, u + m * h2, d, h2)); return o; } __name(ve, "ve"); @@ -975,8 +975,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { return S(typeof n == "string" ? V(n).then((r) => r.json()).then((r) => Ee(e, r)) : J(null, e).then((r) => { let u = {}, c = r.tex.width, s = r.tex.height; for (let o in n) { - let d = n[o], h = { tex: r.tex, frames: ve(d.sliceX, d.sliceY, d.x / c, d.y / s, d.width / c, d.height / s), anims: d.anims }; - U.sprites[o] = h, u[o] = h; + let d = n[o], h2 = { tex: r.tex, frames: ve(d.sliceX, d.sliceY, d.x / c, d.y / s, d.width / c, d.height / s), anims: d.anims }; + U.sprites[o] = h2, u[o] = h2; } return u; })); @@ -1004,8 +1004,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let s = yield Promise.all(c.frames.map(j)), o = document.createElement("canvas"); o.width = c.width, o.height = c.height * c.frames.length; let d = o.getContext("2d"); - return s.forEach((h, m) => { - d.drawImage(h, 0, m * c.height); + return s.forEach((h2, m) => { + d.drawImage(h2, 0, m * c.height); }), J(e, o, { sliceY: c.frames.length, anims: c.anims }); })).then(r).catch(u); })); @@ -1017,9 +1017,9 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { J(e, n).then((s) => { V(r).then((o) => o.json()).then((o) => { let d = o.meta.size; - s.frames = o.frames.map((h) => new k(h.frame.x / d.w, h.frame.y / d.h, h.frame.w / d.w, h.frame.h / d.h)); - for (let h of o.meta.frameTags) - h.from === h.to ? s.anims[h.name] = h.from : s.anims[h.name] = { from: h.from, to: h.to, speed: 10, loop: true }; + s.frames = o.frames.map((h2) => new k(h2.frame.x / d.w, h2.frame.y / d.h, h2.frame.w / d.w, h2.frame.h / d.h)); + for (let h2 of o.meta.frameTags) + h2.from === h2.to ? s.anims[h2.name] = h2.from : s.anims[h2.name] = { from: h2.from, to: h2.to, speed: 10, loop: true }; u(s); }).catch(c); }).catch(c); @@ -1029,26 +1029,26 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { a(Et, "loadAseprite"); function Se(e, n, r, u = false) { function c(s, o, d) { - let h = cn(o, d); - return s && (U.shaders[s] = h), h; + let h2 = cn(o, d); + return s && (U.shaders[s] = h2), h2; } __name(c, "c"); return a(c, "loadRawShader"), S(new Promise((s, o) => { if (!n && !r) return o("no shader"); - function d(h) { - return h ? V(h).then((m) => m.text()).catch(o) : new Promise((m) => m(null)); + function d(h2) { + return h2 ? V(h2).then((m) => m.text()).catch(o) : new Promise((m) => m(null)); } __name(d, "d"); if (a(d, "resolveUrl"), u) - Promise.all([d(n), d(r)]).then(([h, m]) => { - s(c(e, h, m)); + Promise.all([d(n), d(r)]).then(([h2, m]) => { + s(c(e, h2, m)); }).catch(o); else try { s(c(e, n, r)); - } catch (h) { - o(h); + } catch (h2) { + o(h2); } })); } @@ -1095,7 +1095,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { c.connect(s), s.connect(w.masterNode); let o = (g = n.seek) != null ? g : 0; c.start(0, o); - let d = r.currentTime - o, h = null, m = { stop() { + let d = r.currentTime - o, h2 = null, m = { stop() { u || (this.pause(), d = r.currentTime); }, play(y) { if (!u) @@ -1103,9 +1103,9 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let x = c; c = r.createBufferSource(), c.buffer = x.buffer, c.loop = x.loop, c.playbackRate.value = x.playbackRate.value, c.detune && (c.detune.value = x.detune.value), c.connect(s); let W = y != null ? y : this.time(); - c.start(0, W), d = r.currentTime - W, u = false, h = null; + c.start(0, W), d = r.currentTime - W, u = false, h2 = null; }, pause() { - u || (c.stop(), u = true, h = r.currentTime); + u || (c.stop(), u = true, h2 = r.currentTime); }, isPaused() { return u; }, paused() { @@ -1127,7 +1127,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }, duration() { return e.buf.duration; }, time() { - return u ? h - d : r.currentTime - d; + return u ? h2 - d : r.currentTime - d; } }; return m.speed(n.speed), m.detune(n.detune), m.volume(n.volume), m; } @@ -1143,7 +1143,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { r.bindTexture(r.TEXTURE_2D, u), r.texImage2D(r.TEXTURE_2D, 0, r.RGBA, r.RGBA, r.UNSIGNED_BYTE, e); let c = (() => { var o; - switch ((o = n.filter) != null ? o : i.texFilter) { + switch ((o = n.filter) != null ? o : i2.texFilter) { case "linear": return r.LINEAR; case "nearest": @@ -1175,18 +1175,18 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { throw new Error(u); if (u = r.getShaderInfoLog(d)) throw new Error(u); - let h = r.createProgram(); - if (r.attachShader(h, o), r.attachShader(h, d), r.bindAttribLocation(h, 0, "a_pos"), r.bindAttribLocation(h, 1, "a_uv"), r.bindAttribLocation(h, 2, "a_color"), r.linkProgram(h), (u = r.getProgramInfoLog(h)) && u !== ` + let h2 = r.createProgram(); + if (r.attachShader(h2, o), r.attachShader(h2, d), r.bindAttribLocation(h2, 0, "a_pos"), r.bindAttribLocation(h2, 1, "a_uv"), r.bindAttribLocation(h2, 2, "a_color"), r.linkProgram(h2), (u = r.getProgramInfoLog(h2)) && u !== ` `) throw new Error(u); return { bind() { - r.useProgram(h); + r.useProgram(h2); }, unbind() { r.useProgram(null); }, send(m) { this.bind(); for (let g in m) { - let y = m[g], x = r.getUniformLocation(h, g); + let y = m[g], x = r.getUniformLocation(h2, g); typeof y == "number" ? r.uniform1f(x, y) : y instanceof R ? r.uniformMatrix4fv(x, false, new Float32Array(y.m)) : y instanceof v ? r.uniform4f(x, y.r, y.g, y.b, y.a) : y instanceof Fe ? r.uniform3f(x, y.x, y.y, y.z) : y instanceof L && r.uniform2f(x, y.x, y.y); } this.unbind(); @@ -1195,18 +1195,18 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { __name(cn, "cn"); a(cn, "makeShader"); function Nr(e, n, r, u) { - let c = e.width / n, s = e.height / r, o = 1 / c, d = 1 / s, h = {}, m = u.split("").entries(); + let c = e.width / n, s = e.height / r, o = 1 / c, d = 1 / s, h2 = {}, m = u.split("").entries(); for (let [g, y] of m) - h[y] = f(g % c * o, Math.floor(g / c) * d); - return { tex: e, map: h, qw: o, qh: d }; + h2[y] = f(g % c * o, Math.floor(g / c) * d); + return { tex: e, map: h2, qw: o, qh: d }; } __name(Nr, "Nr"); a(Nr, "makeFont"); function Tt(e, n, r, u = l.defTex, c = l.defShader, s = {}) { u = u != null ? u : l.defTex, c = c != null ? c : l.defShader, (u !== l.curTex || c !== l.curShader || !jt(l.curUniform, s) || l.vqueue.length + e.length * je > bt || l.iqueue.length + n.length > bt) && ln(); for (let o of e) { - let d = r ? l.transform : p.cam.transform.mult(l.transform), h = jr(d.multVec2(o.pos.xy())); - l.vqueue.push(h.x, h.y, o.pos.z, o.uv.x, o.uv.y, o.color.r / 255, o.color.g / 255, o.color.b / 255, o.opacity); + let d = r ? l.transform : p.cam.transform.mult(l.transform), h2 = jr(d.multVec2(o.pos.xy())); + l.vqueue.push(h2.x, h2.y, o.pos.z, o.uv.x, o.uv.y, o.color.r / 255, o.color.g / 255, o.color.b / 255, o.opacity); } for (let o of n) l.iqueue.push(o + l.vqueue.length / je - e.length); @@ -1223,7 +1223,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { __name(ln, "ln"); a(ln, "flush"); function He() { - t.gl.clear(t.gl.COLOR_BUFFER_BIT), i.background || pe({ width: F(), height: G(), quad: new k(0, 0, F() * t.scale / Fr, G() * t.scale / Fr), tex: l.bgTex, fixed: true }), l.drawCalls = 0, l.transformStack = [], l.transform = new R(); + t.gl.clear(t.gl.COLOR_BUFFER_BIT), i2.background || pe({ width: F(), height: G(), quad: new k(0, 0, F() * t.scale / Fr, G() * t.scale / Fr), tex: l.bgTex, fixed: true }), l.drawCalls = 0, l.transformStack = [], l.transform = new R(); } __name(He, "He"); a(He, "frameStart"); @@ -1294,12 +1294,12 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { __name(ie, "ie"); a(ie, "popTransform"); function pe(e) { - var h; + var h2; if (e.width === void 0 || e.height === void 0) throw new Error('drawUVQuad() requires property "width" and "height".'); if (e.width <= 0 || e.height <= 0) return; - let n = e.width, r = e.height, c = Ye(e.origin || Ne).scale(f(n, r).scale(-0.5)), s = e.quad || new k(0, 0, 1, 1), o = e.color || E(255, 255, 255), d = (h = e.opacity) != null ? h : 1; + let n = e.width, r = e.height, c = Ye(e.origin || Ne).scale(f(n, r).scale(-0.5)), s = e.quad || new k(0, 0, 1, 1), o = e.color || E(255, 255, 255), d = (h2 = e.opacity) != null ? h2 : 1; re(), I(e.pos), Je(e.angle), ne(e.scale), I(c), Tt([{ pos: de(-n / 2, r / 2, 0), uv: f(e.flipX ? s.x + s.w : s.x, e.flipY ? s.y : s.y + s.h), color: o, opacity: d }, { pos: de(-n / 2, -r / 2, 0), uv: f(e.flipX ? s.x + s.w : s.x, e.flipY ? s.y + s.h : s.y), color: o, opacity: d }, { pos: de(n / 2, -r / 2, 0), uv: f(e.flipX ? s.x : s.x + s.w, e.flipY ? s.y + s.h : s.y), color: o, opacity: d }, { pos: de(n / 2, r / 2, 0), uv: f(e.flipX ? s.x : s.x + s.w, e.flipY ? s.y : s.y + s.h), color: o, opacity: d }], [0, 1, 3, 1, 2, 3], e.fixed, e.tex, e.shader, e.uniform), ie(); } __name(pe, "pe"); @@ -1340,10 +1340,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { a(hn, "drawSprite"); function Re(e, n, r, u, c, s = 1) { u = he(u % 360), c = he(c % 360), c <= u && (c += Math.PI * 2); - let o = Math.ceil(Math.max(Math.sqrt(n + r) * 3 * (s || 1), 16)), d = (c - u) / o, h = []; + let o = Math.ceil(Math.max(Math.sqrt(n + r) * 3 * (s || 1), 16)), d = (c - u) / o, h2 = []; for (let m = u; m < c; m += d) - h.push(e.add(n * Math.cos(m), r * Math.sin(m))); - return h.push(e.add(n * Math.cos(c), r * Math.sin(c))), h; + h2.push(e.add(n * Math.cos(m), r * Math.sin(m))); + return h2.push(e.add(n * Math.cos(c), r * Math.sin(c))), h2; } __name(Re, "Re"); a(Re, "getArcPts"); @@ -1366,8 +1366,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { if (!n || !r) throw new Error('drawLine() requires properties "p1" and "p2".'); let u = e.width || 1, c = r.sub(n).unit().normal().scale(u * 0.5), s = [n.sub(c), n.add(c), r.add(c), r.sub(c)].map((o) => { - var d, h; - return { pos: de(o.x, o.y, 0), uv: f(0), color: (d = e.color) != null ? d : v.WHITE, opacity: (h = e.opacity) != null ? h : 1 }; + var d, h2; + return { pos: de(o.x, o.y, 0), uv: f(0), color: (d = e.color) != null ? d : v.WHITE, opacity: (h2 = e.opacity) != null ? h2 : 1 }; }); Tt(s, [0, 1, 3, 1, 2, 3], e.fixed, l.defTex, e.shader, e.uniform); } @@ -1425,8 +1425,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { if (!(n < 3)) { if (re(), I(e.pos), ne(e.scale), Je(e.angle), I(e.offset), e.fill !== false) { let c = (r = e.color) != null ? r : v.WHITE, s = e.pts.map((d) => { - var h; - return { pos: de(d.x, d.y, 0), uv: f(0, 0), color: c, opacity: (h = e.opacity) != null ? h : 1 }; + var h2; + return { pos: de(d.x, d.y, 0), uv: f(0, 0), color: c, opacity: (h2 = e.opacity) != null ? h2 : 1 }; }), o = [...Array(n - 2).keys()].map((d) => [0, d + 1, d + 2]).flat(); Tt(s, (u = e.indices) != null ? u : o, e.fixed, l.defTex, e.shader, e.uniform); } @@ -1465,10 +1465,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { var Qn, Zn, er; if (e.text === void 0) throw new Error('formatText() requires property "text".'); - let n = gn((Qn = e.font) != null ? Qn : i.font, U.fonts, ws); + let n = gn((Qn = e.font) != null ? Qn : i2.font, U.fonts, ws); if (!n) throw new Error(`Font not found: ${e.font}`); - let { charStyleMap: r, text: u } = $r(e.text + ""), c = u.split(""), s = n.qw * n.tex.width, o = n.qh * n.tex.height, d = e.size || o, h = f(d / o).scale(f(e.scale || 1)), m = h.x * s + ((Zn = e.letterSpacing) != null ? Zn : 0), g = h.y * o + ((er = e.lineSpacing) != null ? er : 0), y = 0, x = g, W = 0, O = [], M = [], ee = null, te = 0; + let { charStyleMap: r, text: u } = $r(e.text + ""), c = u.split(""), s = n.qw * n.tex.width, o = n.qh * n.tex.height, d = e.size || o, h2 = f(d / o).scale(f(e.scale || 1)), m = h2.x * s + ((Zn = e.letterSpacing) != null ? Zn : 0), g = h2.y * o + ((er = e.lineSpacing) != null ? er : 0), y = 0, x = g, W = 0, O = [], M = [], ee = null, te = 0; for (; te < c.length; ) { let oe = c[te]; oe === ` @@ -1483,7 +1483,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { var tr; let Gt = n.map[ct], Yi = ji * m, $i = Bi * g; if (Gt) { - let _t = { tex: n.tex, quad: new k(Gt.x, Gt.y, n.qw, n.qh), ch: ct, pos: f(Oe.x + Yi + se + Ni, Oe.y + $i + Ie), opacity: e.opacity, color: (tr = e.color) != null ? tr : E(255, 255, 255), scale: h, angle: 0, uniform: e.uniform, fixed: e.fixed }; + let _t = { tex: n.tex, quad: new k(Gt.x, Gt.y, n.qw, n.qh), ch: ct, pos: f(Oe.x + Yi + se + Ni, Oe.y + $i + Ie), opacity: e.opacity, color: (tr = e.color) != null ? tr : E(255, 255, 255), scale: h2, angle: 0, uniform: e.uniform, fixed: e.fixed }; if (e.transform) { let lt = typeof e.transform == "function" ? e.transform(ut, ct) : e.transform; lt && mn(_t, lt); @@ -1517,33 +1517,33 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { function Un() { let e = t.gl, n = e.drawingBufferWidth, r = e.drawingBufferHeight, u = F(), c = G(); if (vn()) { - let s = window.innerWidth, o = window.innerHeight, d = s / o, h = n / r; - if (d > h) { - let m = window.innerHeight * h; + let s = window.innerWidth, o = window.innerHeight, d = s / o, h2 = n / r; + if (d > h2) { + let m = window.innerHeight * h2; l.viewport = { x: (s - m) / 2, y: 0, width: m, height: o }; } else { - let m = window.innerWidth / h; + let m = window.innerWidth / h2; l.viewport = { x: 0, y: (o - m) / 2, width: s, height: m }; } return; } - if (i.letterbox) { - if (!i.width || !i.height) + if (i2.letterbox) { + if (!i2.width || !i2.height) throw new Error("Letterboxing requires width and height defined."); - let s = n / r, o = i.width / i.height; + let s = n / r, o = i2.width / i2.height; if (s > o) { - i.stretch || (l.width = r * o, l.height = r); - let d = r * o, h = r, m = (n - d) / 2; - e.scissor(m, 0, d, h), e.viewport(m, 0, d, r), l.viewport = { x: m, y: 0, width: d, height: r }; + i2.stretch || (l.width = r * o, l.height = r); + let d = r * o, h2 = r, m = (n - d) / 2; + e.scissor(m, 0, d, h2), e.viewport(m, 0, d, r), l.viewport = { x: m, y: 0, width: d, height: r }; } else { - i.stretch || (l.width = n, l.height = n / o); - let d = n, h = n / o, m = (r - h) / 2; - e.scissor(0, m, d, h), e.viewport(0, m, n, h), l.viewport = { x: 0, y: m, width: n, height: h }; + i2.stretch || (l.width = n, l.height = n / o); + let d = n, h2 = n / o, m = (r - h2) / 2; + e.scissor(0, m, d, h2), e.viewport(0, m, n, h2), l.viewport = { x: 0, y: m, width: n, height: h2 }; } return; } - if (i.stretch) { - if (!i.width || !i.height) + if (i2.stretch) { + if (!i2.width || !i2.height) throw new Error("Stretching requires width and height defined."); e.viewport(0, 0, n, r), l.viewport = { x: 0, y: 0, width: n, height: r }; return; @@ -1576,21 +1576,21 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let n = Pr[e.key] || e.key.toLowerCase(); t.keyStates[n] = "released", t.isKeyReleased = true; }), t.canvas.addEventListener("touchstart", (e) => { - if (!i.touchToMouse) + if (!i2.touchToMouse) return; e.preventDefault(); let n = e.touches[0]; t.mousePos = f(n.clientX, n.clientY).scale(1 / t.scale), t.mouseStates.left = "pressed"; }), t.canvas.addEventListener("touchmove", (e) => { - if (!i.touchToMouse) + if (!i2.touchToMouse) return; e.preventDefault(); let n = e.touches[0]; t.mousePos = f(n.clientX, n.clientY).scale(1 / t.scale), t.isMouseMoved = true; }), t.canvas.addEventListener("touchend", (e) => { - !i.touchToMouse || (t.mouseStates.left = "released"); + !i2.touchToMouse || (t.mouseStates.left = "released"); }), t.canvas.addEventListener("touchcancel", (e) => { - !i.touchToMouse || (t.mouseStates.left = "released"); + !i2.touchToMouse || (t.mouseStates.left = "released"); }), t.canvas.addEventListener("touchstart", (e) => { [...e.changedTouches].forEach((n) => { p.trigger("onTouchStart", n.identifier, f(n.clientX, n.clientY).scale(1 / t.scale)); @@ -1779,24 +1779,24 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { s.id && (this.unuse(s.id), n.set(s.id, {})); let o = s.id ? n.get(s.id) : r; o.cleanups = []; - for (let h in s) - if (!ys.has(h)) { - if (typeof s[h] == "function") { - let m = s[h].bind(this); - if (bs.has(h)) { - o.cleanups.push(this.on(h, m)), o[h] = m; + for (let h2 in s) + if (!ys.has(h2)) { + if (typeof s[h2] == "function") { + let m = s[h2].bind(this); + if (bs.has(h2)) { + o.cleanups.push(this.on(h2, m)), o[h2] = m; continue; } else - o[h] = m; + o[h2] = m; } else - o[h] = s[h]; - this[h] === void 0 && Object.defineProperty(this, h, { get: () => o[h], set: (m) => o[h] = m, configurable: true, enumerable: true }); + o[h2] = s[h2]; + this[h2] === void 0 && Object.defineProperty(this, h2, { get: () => o[h2], set: (m) => o[h2] = m, configurable: true, enumerable: true }); } let d = a(() => { if (!!s.require) { - for (let h of s.require) - if (!this.c(h)) - throw new Error(`comp '${s.id}' requires comp '${h}'`); + for (let h2 of s.require) + if (!this.c(h2)) + throw new Error(`comp '${s.id}' requires comp '${h2}'`); } }, "checkDeps"); this.exists() ? (s.add && s.add.call(this), s.load && Ue(() => s.load.call(this)), d()) : s.require && o.cleanups.push(this.on("add", d)); @@ -1813,8 +1813,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }, get(s) { return this.children.filter((o) => s ? o.is(s) : true).sort((o, d) => { var g, y, x, W, O, M; - let h = (y = p.layers[(g = o.layer) != null ? g : p.defLayer]) != null ? y : 0, m = (W = p.layers[(x = d.layer) != null ? x : p.defLayer]) != null ? W : 0; - return h == m ? ((O = o.z) != null ? O : 0) - ((M = d.z) != null ? M : 0) : h - m; + let h2 = (y = p.layers[(g = o.layer) != null ? g : p.defLayer]) != null ? y : 0, m = (W = p.layers[(x = d.layer) != null ? x : p.defLayer]) != null ? W : 0; + return h2 == m ? ((O = o.z) != null ? O : 0) - ((M = d.z) != null ? M : 0) : h2 - m; }); }, every(s, o) { if (typeof s == "function" && o === void 0) @@ -1844,10 +1844,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }, action(...s) { return console.warn("action() is deprecated. Use onUpdate() instead"), this.onUpdate(...s); }, trigger(s, ...o) { - u[s] && u[s].forEach((h) => h.call(this, ...o)); + u[s] && u[s].forEach((h2) => h2.call(this, ...o)); let d = p.objEvents[s]; - d && d.forEach((h) => { - this.is(h.tag) && h.cb(this, ...o); + d && d.forEach((h2) => { + this.is(h2.tag) && h2.cb(this, ...o); }); }, destroy() { var s; @@ -1894,7 +1894,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { __name(Tn, "Tn"); a(Tn, "onDraw"); function Sn(e, n, r) { - let u = qe("collide", e, (o, d, h) => d.is(n) && r(o, d, h)), c = qe("collide", n, (o, d, h) => d.is(e) && r(d, o, h)), s = Pe(e, (o) => { + let u = qe("collide", e, (o, d, h2) => d.is(n) && r(o, d, h2)), c = qe("collide", n, (o, d, h2) => d.is(e) && r(d, o, h2)), s = Pe(e, (o) => { if (!o.area) throw new Error("onCollide() requires the object to have area() component"); o._checkCollisions(n, (d) => { @@ -2062,11 +2062,11 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let r = f(...n), u = r.x, c = r.y, s = null; if (this.solid && ((o = this.area) == null ? void 0 : o.shape) === "rect") { let d = this.worldArea(); - p.root.every((h) => { + p.root.every((h2) => { var Oe; - if (!this.exists() || h === this || !h.solid || ((Oe = h.area) == null ? void 0 : Oe.shape) !== "rect") + if (!this.exists() || h2 === this || !h2.solid || ((Oe = h2.area) == null ? void 0 : Oe.shape) !== "rect") return; - let m = h.worldArea(), g = yt(m, d); + let m = h2.worldArea(), g = yt(m, d); if (ae(g, f(0))) { let _ = Math.min(Math.abs(g.p1.x), Math.abs(g.p2.x), Math.abs(g.p1.y), Math.abs(g.p2.y)), se = (() => { switch (_) { @@ -2094,7 +2094,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } if (x < 1 && !(x === 0 && te == 1 && !ae(g, f(u, c)))) { let _ = f(-u * (1 - x), -c * (1 - x)); - u *= x, c *= x, s = kn(h, _); + u *= x, c *= x, s = kn(h2, _); } }); } @@ -2256,8 +2256,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { this.isHovering() ? c() : s && s(); }); }, onCollide(c, s) { - let o = this.onUpdate(() => this._checkCollisions(c, s)), d = this.on("collide", (h, m) => h.is(c) && s(h, m)); - return () => [o, d].forEach((h) => h()); + let o = this.onUpdate(() => this._checkCollisions(c, s)), d = this.on("collide", (h2, m) => h2.is(c) && s(h2, m)); + return () => [o, d].forEach((h2) => h2()); }, clicks(...c) { return B("clicks()", "onClick()"), this.onClick(...c); }, hovers(...c) { @@ -2273,16 +2273,16 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let s = this.worldArea(), o = c.worldArea(), d = yt(s, o); if (!ae(d, f(0))) return null; - let h = Math.min(Math.abs(d.p1.x), Math.abs(d.p2.x), Math.abs(d.p1.y), Math.abs(d.p2.y)), m = (() => { - switch (h) { + let h2 = Math.min(Math.abs(d.p1.x), Math.abs(d.p2.x), Math.abs(d.p1.y), Math.abs(d.p2.y)), m = (() => { + switch (h2) { case Math.abs(d.p1.x): - return f(h, 0); + return f(h2, 0); case Math.abs(d.p2.x): - return f(-h, 0); + return f(-h2, 0); case Math.abs(d.p1.y): - return f(0, h); + return f(0, h2); case Math.abs(d.p2.y): - return f(0, -h); + return f(0, -h2); } })(); this.pos = this.pos.add(m); @@ -2303,8 +2303,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { throw new Error("failed to get area dimension"); let o = f((y = this.scale) != null ? y : 1).scale(this.area.scale); c *= o.x, s *= o.y; - let d = Ye(this.origin || Ne), h = ((x = this.pos) != null ? x : f(0)).add(this.area.offset).sub(d.add(1, 1).scale(0.5).scale(c, s)); - return { shape: "rect", p1: h, p2: f(h.x + c, h.y + s) }; + let d = Ye(this.origin || Ne), h2 = ((x = this.pos) != null ? x : f(0)).add(this.area.offset).sub(d.add(1, 1).scale(0.5).scale(c, s)); + return { shape: "rect", p1: h2, p2: f(h2.x + c, h2.y + s) }; }, screenArea() { let c = this.worldArea(); return this.fixed ? c : { shape: "rect", p1: rt(c.p1), p2: rt(c.p2) }; @@ -2320,9 +2320,9 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { function Vt(e, n = {}) { var s; let r = null, u = null; - function c(o, d, h, m) { + function c(o, d, h2, m) { let g = f(1, 1); - return h && m ? (g.x = h / (o.width * d.w), g.y = m / (o.height * d.h)) : h ? (g.x = h / (o.width * d.w), g.y = g.x) : m && (g.y = m / (o.height * d.h), g.x = g.y), g; + return h2 && m ? (g.x = h2 / (o.width * d.w), g.y = m / (o.height * d.h)) : h2 ? (g.x = h2 / (o.width * d.w), g.y = g.x) : m && (g.y = m / (o.height * d.h), g.x = g.y), g; } __name(c, "c"); return a(c, "calcTexScale"), { id: "sprite", width: 0, height: 0, frame: n.frame || 0, quad: n.quad || new k(0, 0, 1, 1), animSpeed: (s = n.animSpeed) != null ? s : 1, load() { @@ -2353,11 +2353,11 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }); return; } - let h = r.anims[o]; - if (h == null) + let h2 = r.anims[o]; + if (h2 == null) throw new Error(`anim not found: ${o}`); - u && this.stop(), u = { name: o, timer: 0, loop: (g = (m = d.loop) != null ? m : h.loop) != null ? g : false, pingpong: (x = (y = d.pingpong) != null ? y : h.pingpong) != null ? x : false, speed: (O = (W = d.speed) != null ? W : h.speed) != null ? O : 10, onEnd: (M = d.onEnd) != null ? M : () => { - } }, typeof h == "number" ? this.frame = h : this.frame = h.from, this.trigger("animPlay", o), this.trigger("animStart", o); + u && this.stop(), u = { name: o, timer: 0, loop: (g = (m = d.loop) != null ? m : h2.loop) != null ? g : false, pingpong: (x = (y = d.pingpong) != null ? y : h2.pingpong) != null ? x : false, speed: (O = (W = d.speed) != null ? W : h2.speed) != null ? O : 10, onEnd: (M = d.onEnd) != null ? M : () => { + } }, typeof h2 == "number" ? this.frame = h2 : this.frame = h2.from, this.trigger("animPlay", o), this.trigger("animStart", o); }, stop() { if (!u) return; @@ -2372,12 +2372,12 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }, flipY(o) { n.flipY = o; }, onAnimEnd(o, d) { - return this.on("animEnd", (h) => { - h === o && d(); + return this.on("animEnd", (h2) => { + h2 === o && d(); }); }, onAnimStart(o, d) { - return this.on("animStart", (h) => { - h === o && d(); + return this.on("animStart", (h2) => { + h2 === o && d(); }); }, inspect() { let o = ""; @@ -2451,10 +2451,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let n = 0, r = null, u = null, c = true; return { id: "body", require: ["pos", "area"], jumpForce: (s = e.jumpForce) != null ? s : yi, weight: (o = e.weight) != null ? o : 1, solid: (d = e.solid) != null ? d : true, update() { var m; - let h = false; + let h2 = false; if (r) { let g = this.worldArea(), y = r.worldArea(), x = g.p2.y, W = y.p1.y, O = g.p1.x, M = g.p2.x, ee = y.p1.x, te = y.p2.x; - !r.exists() || x !== W || M < ee || O > te ? (this.trigger("fall", r), r = null, u = null, h = true) : u && r.pos && (this.pos = this.pos.add(r.pos.sub(u)), u = r.pos.clone()); + !r.exists() || x !== W || M < ee || O > te ? (this.trigger("fall", r), r = null, u = null, h2 = true) : u && r.pos && (this.pos = this.pos.add(r.pos.sub(u)), u = r.pos.clone()); } if (!r) { let g = this.move(0, n); @@ -2462,7 +2462,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { if (g.isBottom()) { r = g.target; let y = n; - n = 0, r.pos && (u = r.pos.clone()), h || (this.trigger("ground", r), c = true); + n = 0, r.pos && (u = r.pos.clone()), h2 || (this.trigger("ground", r), c = true); } else g.isTop() && (n = 0, this.trigger("headbutt", g.target)); n += Fn() * this.weight * Z(), n = Math.min(n, (m = e.maxVel) != null ? m : bi); @@ -2477,18 +2477,18 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { return n > 0; }, falling() { return B("falling()", "isFalling()"), this.isFalling(); - }, jump(h) { - r = null, u = null, n = -h || -this.jumpForce; - }, doubleJump(h) { - this.isGrounded() ? this.jump(h) : c && (c = false, this.jump(h), this.trigger("doubleJump")); - }, onGround(h) { - return this.on("ground", h); - }, onFall(h) { - return this.on("fall", h); - }, onHeadbutt(h) { - return this.on("headbutt", h); - }, onDoubleJump(h) { - return this.on("doubleJump", h); + }, jump(h2) { + r = null, u = null, n = -h2 || -this.jumpForce; + }, doubleJump(h2) { + this.isGrounded() ? this.jump(h2) : c && (c = false, this.jump(h2), this.trigger("doubleJump")); + }, onGround(h2) { + return this.on("ground", h2); + }, onFall(h2) { + return this.on("fall", h2); + }, onHeadbutt(h2) { + return this.on("headbutt", h2); + }, onDoubleJump(h2) { + return this.on("doubleJump", h2); } }; } __name(xi, "xi"); @@ -2557,16 +2557,16 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } __name(c, "c"); a(c, "initStateHook"); - function s(d, h, m) { - c(h), u[h][d].push(m); + function s(d, h2, m) { + c(h2), u[h2][d].push(m); } __name(s, "s"); a(s, "on"); - function o(d, h, ...m) { - c(h), u[h][d].forEach((g) => g(...m)); + function o(d, h2, ...m) { + c(h2), u[h2][d].forEach((g) => g(...m)); } __name(o, "o"); - return a(o, "trigger"), { id: "state", state: e, enterState(d, ...h) { + return a(o, "trigger"), { id: "state", state: e, enterState(d, ...h2) { if (n && !n.includes(d)) throw new Error(`State not found: ${d}`); let m = this.state; @@ -2577,17 +2577,17 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { if (!g.includes(d)) throw new Error(`Cannot transition state from "${m}" to "${d}". Available transitions: ${g.map((y) => `"${y}"`).join(", ")}`); } - o("leave", m, ...h), this.state = d, o("enter", d, ...h), o("enter", `${m} -> ${d}`, ...h); - }, onStateTransition(d, h, m) { - s("enter", `${d} -> ${h}`, m); - }, onStateEnter(d, h) { - s("enter", d, h); - }, onStateUpdate(d, h) { - s("update", d, h); - }, onStateDraw(d, h) { - s("draw", d, h); - }, onStateLeave(d, h) { - s("leave", d, h); + o("leave", m, ...h2), this.state = d, o("enter", d, ...h2), o("enter", `${m} -> ${d}`, ...h2); + }, onStateTransition(d, h2, m) { + s("enter", `${d} -> ${h2}`, m); + }, onStateEnter(d, h2) { + s("enter", d, h2); + }, onStateUpdate(d, h2) { + s("update", d, h2); + }, onStateDraw(d, h2) { + s("draw", d, h2); + }, onStateLeave(d, h2) { + s("leave", d, h2); }, update() { o("update", this.state); }, draw() { @@ -2614,7 +2614,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let r = p.on("updateStart", () => { p.events = {}, p.objEvents = { add: new $(), update: new $(), draw: new $(), destroy: new $() }, p.root.every((u) => { u.is("stay") || p.root.remove(u); - }), p.root.clearEvents(), p.timers = new $(), p.cam = { pos: ot(), scale: f(1), angle: 0, shake: 0, transform: new R() }, p.layers = {}, p.defLayer = null, p.gravity = Vr, p.scenes[e](...n), i.debug !== false && In(), i.burp && Vn(), r(); + }), p.root.clearEvents(), p.timers = new $(), p.cam = { pos: ot(), scale: f(1), angle: 0, shake: 0, transform: new R() }, p.layers = {}, p.defLayer = null, p.gravity = Vr, p.scenes[e](...n), i2.debug !== false && In(), i2.burp && Vn(), r(); }); } __name(Mi, "Mi"); @@ -2636,7 +2636,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { function Bn(e) { let n = e(ye); for (let r in n) - ye[r] = n[r], i.global !== false && (window[r] = n[r]); + ye[r] = n[r], i2.global !== false && (window[r] = n[r]); return ye; } __name(Bn, "Bn"); @@ -2675,23 +2675,23 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { let d = f(...o); return f(u.x + d.x * n.width, u.y + d.y * n.height); }, spawn(o, ...d) { - let h = f(...d), m = (() => { + let h2 = f(...d), m = (() => { if (n[o]) { if (typeof n[o] != "function") throw new Error("level symbol def must be a function returning a component list"); - return n[o](h); + return n[o](h2); } else if (n.any) - return n.any(o, h); + return n.any(o, h2); })(); if (!m) return; - let g = f(u.x + h.x * n.width, u.y + h.y * n.height); + let g = f(u.x + h2.x * n.width, u.y + h2.y * n.height); for (let x of m) if (x.id === "pos") { g.x += x.pos.x, g.y += x.pos.y; break; } - m.push(it(g)), m.push(qi(this, h)); + m.push(it(g)), m.push(qi(this, h2)); let y = p.root.add(m); return r.push(y), y; }, width() { @@ -2703,8 +2703,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { o.destroy(); } }; return e.forEach((o, d) => { - let h = o.split(""); - c = Math.max(h.length, c), h.forEach((m, g) => { + let h2 = o.split(""); + c = Math.max(h2.length, c), h2.forEach((m, g) => { s.spawn(m, f(g, d)); }); }), s; @@ -2718,7 +2718,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { return s.ondataavailable = (d) => { d.data.size > 0 && o.push(d.data); }, s.onerror = (d) => { - w.masterNode.disconnect(r), n.getTracks().forEach((h) => h.stop()); + w.masterNode.disconnect(r), n.getTracks().forEach((h2) => h2.stop()); }, s.start(), { resume() { s.resume(); }, pause() { @@ -2726,7 +2726,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { }, download(d = "kaboom.mp4") { s.onstop = () => { ur(new Blob(o, { type: "video/mp4" }), d); - }, s.stop(), w.masterNode.disconnect(r), n.getTracks().forEach((h) => h.stop()); + }, s.stop(), w.masterNode.disconnect(r), n.getTracks().forEach((h2) => h2.stop()); } }; } __name(Nn, "Nn"); @@ -2836,13 +2836,13 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { function zn() { var e, n; if (C.inspect) { - let r = null, u = v.fromArray((e = i.inspectColor) != null ? e : [0, 0, 255]); + let r = null, u = v.fromArray((e = i2.inspectColor) != null ? e : [0, 0, 255]); if (p.root.every((c) => { if (!c.area || c.hidden) return; r || c.isHovering() && (r = c); - let s = r === c ? 8 : 4, o = c.worldArea(), d = o.p2.x - o.p1.x, h = o.p2.y - o.p1.y; - Q({ pos: o.p1, width: d, height: h, outline: { width: s, color: u }, fill: false, fixed: c.fixed }); + let s = r === c ? 8 : 4, o = c.worldArea(), d = o.p2.x - o.p1.x, h2 = o.p2.y - o.p1.y; + Q({ pos: o.p1, width: d, height: h2, outline: { width: s, color: u }, fill: false, fixed: c.fixed }); }), r) { let c = [], s = r.inspect(); for (let o in s) @@ -2872,7 +2872,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } if (C.curRecording && (re(), I(0, G()), ne(1 / t.scale), I(24, -24), Ct({ radius: 12, color: E(255, 0, 0), opacity: Kt(0, 1, nt() * 4), fixed: true }), ie()), C.showLog && p.logs.length > 0) { re(), I(0, G()), ne(1 / t.scale), I(8, -8); - let r = 8, u = (n = i.logMax) != null ? n : 1; + let r = 8, u = (n = i2.logMax) != null ? n : 1; p.logs.length > u && (p.logs = p.logs.slice(0, u)); let c = me({ text: p.logs.join(` `), font: U.fonts[rn], pos: f(r, -r), origin: "botleft", size: 16, width: F() * t.scale * 0.6, lineSpacing: r / 2, fixed: true, styles: { time: { color: E(127, 127, 127) }, info: { color: E(255, 255, 255) }, error: { color: E(255, 0, 127) } } }); @@ -2880,7 +2880,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } } __name(zn, "zn"); - a(zn, "drawDebug"), i.debug !== false && In(), i.burp && Vn(), window.addEventListener("error", (e) => { + a(zn, "drawDebug"), i2.debug !== false && In(), i2.burp && Vn(), window.addEventListener("error", (e) => { C.error(`Error: ${e.error.message}`), zr(), Jn(() => { A() === 1 && (He(), zn(), ze()); }); @@ -2903,15 +2903,179 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } __name(Jn, "Jn"); a(Jn, "run"), Jn(() => { - Un(), t.loaded ? (p.trigger("input"), C.paused || Kn(), He(), Gi(), i.debug !== false && zn(), ze()) : (He(), _i(), ze()); + Un(), t.loaded ? (p.trigger("input"), C.paused || Kn(), He(), Gi(), i2.debug !== false && zn(), ze()) : (He(), _i(), ze()); }), X("apl386", Tr, 45, 74), X("apl386o", Sr, 45, 74), X("sink", Cr, 6, 8, { chars: "\u2588\u263A\u263B\u2665\u2666\u2663\u2660\u25CF\u25CB\u25AA\u25A1\u25A0\u25D8\u266A\u266B\u2261\u25BA\u25C4\u2302\xDE\xC0\xDF\xD7\xA5\u2191\u2193\u2192\u2190\u25CC\u25CF\u25BC\u25B2 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u03A7\u2591\u2592\u2593\u1E00\u1E01\u1E02\u2502\u252C\u2524\u250C\u2510\u1E03\u1E04\u253C\u1E05\u1E06\u1E07\u1E08\u1E09\u1E0A\u1E0B\u1E0C\u2500\u251C\u2534\u2514\u2518\u1E0D\u1E0E\u205E\u1E0F\u1E10\u1E11\u1E12\u1E13\u1E14\u1E15\u1E16\u1E17\u1E18\u2584\u1E19\u1E1A\u1E1B\u1E1C\u2026\u1E1D\u1E1E\u1E1F\u1E20\u1E21\u1E22\u1E23\u1E24\u1E25\u1E26\u258C\u2590\u1E27\u1E28\u1E29\u1E2A\u1E2B\u1E2C\u1E2D\u1E2E\u1E2F\u1E30\u1E31\u1E32\u1E33\u1E34\u1E35\u1E36\u1E37\u1E38\u1E39\u1E3A\u1E3B\u1E3C\u1E3D\u1E3E\u1E3F\u1E40\u1E41\u1E42\u1E43\u1E44\u1E45\u1E46\u1E47\u1E48\u1E49\u1E4A\u1E4B\u1E4C\u1E4D\u1E4E\u1E4F\u1E50\u1E51\u1E52\u1E53\u1E54\u1E55\u1E56\u1E57\u1E58\u1E59\u1E5A\u1E5B\u1E5C\u1E5D\u1E5E\u1E5F\u1E60\u1E61\u1E62\u1E63\u1E64\u1E65\u1E66\u1E67\u1E68\u1E69\u1E6A\u1E6B\u1E6C\u1E6D\u1E6E\u1E6F\u1E70\u1E71\u1E72\u1E73\u1E74\u1E75\u1E76\u1E77\u1E78\u1E79\u1E7A\u1E7B\u1E7C" }), X("sinko", Rr, 8, 10), He(), ze(); let ye = { loadRoot: q, loadSprite: J, loadSpriteAtlas: Ee, loadSound: Ce, loadFont: X, loadShader: Se, loadAseprite: Et, loadPedit: $e, loadBean: _r, load: S, width: F, height: G, center: ot, dt: Z, time: nt, screenshot: bn, record: Nn, isFocused: jn, focus: Di, cursor: xn, regCursor: ii, fullscreen: Hr, isFullscreen: vn, onLoad: Ue, isTouch: () => t.isTouch, layers: Qr, camPos: Zr, camScale: ei, camRot: ti, shake: ni, toScreen: rt, toWorld: Lt, gravity: Fn, add: kt, readd: Ai, destroy: at, destroyAll: Oi, get: Ii, every: Vi, revery: Fi, pos: it, scale: st, rotate: si, color: oi, opacity: ai, origin: Ot, layer: ui, area: fi, sprite: Vt, text: pi, rect: mi, circle: gi, uvquad: wi, outline: Ui, body: xi, shader: vi, timer: Gn, solid: Ei, fixed: Ti, stay: Ft, health: Si, lifespan: Ci, z: ci, move: hi, outview: It, cleanup: di, follow: li, state: Ri, on: qe, onUpdate: Pe, onDraw: Tn, onCollide: Sn, onClick: Cn, onHover: Rn, onKeyDown: qt, onKeyPress: H, onKeyPressRepeat: Pt, onKeyRelease: Dt, onMouseDown: Mn, onMousePress: At, onMouseRelease: Ln, onMouseMove: qn, onCharInput: Pn, onTouchStart: Dn, onTouchMove: An, onTouchEnd: On, mousePos: K, mouseWorldPos: Jr, mouseDeltaPos: yn, isKeyDown: Mt, isKeyPressed: Le, isKeyPressedRepeat: Wt, isKeyReleased: tt, isMouseDown: Ze, isMousePressed: Me, isMouseReleased: et, isMouseMoved: Rt, loop: ri, wait: Wn, play: Xe, volume: Br, burp: un, audioCtx: w.ctx, Timer: fe, Line: le, Rect: ke, Circle: ht, Vec2: L, Color: v, Mat4: R, Quad: k, RNG: be, rng: mr, rand: Ge, randi: Ht, randSeed: wr, vec2: f, rgb: E, hsl2rgb: fr, quad: pr, choose: Ur, chance: gr, lerp: Ve, map: dt, mapc: dr, wave: Kt, deg2rad: he, rad2deg: Xt, testAreaRect: gt, testAreaLine: Zt, testAreaCircle: en, testAreaPolygon: tn, testAreaPoint: Ut, testAreaArea: nn, testLineLine: ce, testRectRect: zt, testRectLine: ft, testRectPoint: ae, testPolygonPoint: xe, testLinePolygon: _e, testPolygonPolygon: wt, testCircleCircle: Qt, testCirclePoint: mt, testRectPolygon: pt, drawSprite: hn, drawText: Xr, formatText: me, drawRect: Q, drawLine: We, drawLines: dn, drawTriangle: fn, drawCircle: Ct, drawEllipse: pn, drawUVQuad: pe, drawPolygon: Qe, drawFormattedText: we, pushTransform: re, popTransform: ie, pushTranslate: I, pushRotate: Je, pushScale: ne, debug: C, scene: Wi, go: Mi, addLevel: Pi, getData: Li, setData: _n, plug: Bn, ASCII_CHARS: Ar, CP437_CHARS: hs, canvas: t.canvas, addKaboom: ki, LEFT: L.LEFT, RIGHT: L.RIGHT, UP: L.UP, DOWN: L.DOWN, RED: v.RED, GREEN: v.GREEN, BLUE: v.BLUE, YELLOW: v.YELLOW, MAGENTA: v.MAGENTA, CYAN: v.CYAN, WHITE: v.WHITE, BLACK: v.BLACK, keyIsDown: T("keyIsDown()", "isKeyDown()", Mt), keyIsPressed: T("keyIsPressed()", "isKeyPressed()", Le), keyIsPressedRep: T("keyIsPressedRep()", "isKeyPressedRepeat()", Wt), keyIsReleased: T("keyIsReleased()", "isKeyReleased()", tt), mouseIsDown: T("mouseIsDown()", "isMouseDown()", Ze), mouseIsClicked: T("mouseIsClicked()", "isMousePressed()", Me), mouseIsReleased: T("mouseIsReleased()", "isMouseReleased()", et), mouseIsMoved: T("mouseIsMoved()", "isMouseMoved()", Rt), dir: T("dir()", "Vec2.fromAngle()", L.fromAngle), action: T("action()", "onUpdate()", Pe), render: T("render()", "onDraw()", Tn), collides: T("collides()", "onCollide()", Sn), clicks: T("clicks()", "onClick()", Cn), hovers: T("hovers()", "onHover()", Rn), keyDown: T("keyDown()", "onKeyDown()", qt), keyPress: T("keyPress()", "onKeyPress()", H), keyPressRep: T("keyPressRep()", "onKeyPressRepeat()", Pt), keyRelease: T("keyRelease()", "onKeyRelease()", Dt), mouseDown: T("mouseDown()", "onMouseDown()", Mn), mouseClick: T("mouseClick()", "onMousePress()", At), mouseRelease: T("mouseRelease()", "onMouseRelease()", Ln), mouseMove: T("mouseMove()", "onMouseMove()", qn), charInput: T("charInput()", "onCharInput()", Pn), touchStart: T("touchStart()", "onTouchStart()", Dn), touchMove: T("touchMove()", "onTouchMove()", An), touchEnd: T("touchEnd()", "onTouchEnd()", On), focused: T("focused()", "isFocused()", jn), ready: T("ready()", "onLoad()", Ue) }; - if (i.plugins && i.plugins.forEach(Bn), i.global !== false) + if (i2.plugins && i2.plugins.forEach(Bn), i2.global !== false) for (let e in ye) window[e] = ye[e]; return ye; }, "default"); + // code/easing.js + var easings = { + linear: (x) => { + return x; + }, + easeInSine: (x) => { + return 1 - Math.cos(x * Math.PI / 2); + }, + easeOutSine: (x) => { + return Math.sin(x * Math.PI / 2); + }, + easeInOutSine: (x) => { + return -(Math.cos(Math.PI * x) - 1) / 2; + }, + easeInQuad: (x) => { + return x * x; + }, + easeOutQuad: (x) => { + return 1 - (1 - x) * (1 - x); + }, + easeInOutQuad: (x) => { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + }, + easeInCubic: (x) => { + return x * x * x; + }, + easeOutCubic: (x) => { + return 1 - Math.pow(1 - x, 3); + }, + easeInOutCubic: (x) => { + return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + }, + easeInQuart: (x) => { + return x * x * x * x; + }, + easeOutQuart: (x) => { + return 1 - Math.pow(1 - x, 4); + }, + easeInOutQuart: (x) => { + return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; + }, + easeInQuint: (x) => { + return x * x * x * x * x; + }, + easeOutQuint: (x) => { + return 1 - Math.pow(1 - x, 5); + }, + easeInOutQuint: (x) => { + return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; + }, + easeInExpo: (x) => { + return x === 0 ? 0 : Math.pow(2, 10 * x - 10); + }, + easeOutExpo: (x) => { + return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); + }, + easeInOutExpo: (x) => { + return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2; + }, + easeInCirc: (x) => { + return 1 - Math.sqrt(1 - Math.pow(x, 2)); + }, + easeOutCirc: (x) => { + return Math.sqrt(1 - Math.pow(x - 1, 2)); + }, + easeInOutCirc: (x) => { + return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; + }, + easeInBack: (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + return c3 * x * x * x - c1 * x * x; + }, + easeOutBack: (x) => { + const c1 = 1.70158; + const c3 = c1 + 1; + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }, + easeInOutBack: (x) => { + const c1 = 1.70158; + const c2 = c1 * 1.525; + return x < 0.5 ? Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2) / 2 : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + }, + easeInBounce: (x) => { + return 1 - easings.easeOutBounce(1 - x); + }, + easeOutBounce: (x) => { + const n1 = 7.5625; + const d1 = 2.75; + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + }, + easeInOutBounce: (x) => { + return x < 0.5 ? (1 - easings.easeOutBounce(1 - 2 * x)) / 2 : (1 + easings.easeOutBounce(2 * x - 1)) / 2; + } + }; + var tweentypes = { + FOREVER: (t, st, tl) => { + if (t - st >= tl) { + return "FOREVER"; + } + return "CONTINUE"; + }, + LERPFOREVER: (t, st, tl) => { + return "LF"; + }, + PINGPONG: (t, st, tl) => { + if (t - st >= tl) { + return "PING"; + } + return "CONTINUE"; + }, + NORMAL: (t, st, tl) => { + if (t - st >= tl) { + return "CALLBACK"; + } + return "CONTINUE"; + } + }; + function tween(func, attrs, timeLen, minVal, maxVal, ease, type, onFinish) { + var minVal = minVal != void 0 ? minVal : func[attrs[0]]; + var ease = ease != void 0 ? ease : easings.linear; + var type = type != void 0 ? type : tweentypes.NORMAL; + var stTime = time(); + var onFinish = onFinish != void 0 ? onFinish : "ud"; + var upd = onUpdate(() => { + switch (type(time(), stTime, timeLen)) { + case "CALLBACK": + for (h in attrs) { + func[attrs[h]] = maxVal; + } + upd(); + return onFinish == "ud" ? true : onFinish(); + case "FOREVER": + stTime = time(); + break; + case "CONTINUE": + for (h in attrs) { + func[attrs[h]] = minVal; + } + break; + case "PING": + var buffer = minVal; + minVal = maxVal; + maxVal = buffer; + stTime = time(); + break; + default: + break; + } + for (i in attrs) { + func[attrs[i]] = lerp(minVal, maxVal, ease((time() - stTime) / timeLen)); + } + }); + } + __name(tween, "tween"); + // code/main.js no({ width: 700, @@ -2924,6 +3088,8 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { loadFont("unscii", "fonts/unscii_8x8.png", 8, 8, { chars: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" }); loadSound("tutorial", "sounds/Getting it Done.mp3"); loadSound("faith", "sounds/The Friendly Faith Plate.mp3"); + loadSound("sonic", "sounds/SonicInMidSim.wav"); + loadSound("green", "sounds/GreenHill.wav"); loadSound("gameover", "sounds/gameover.mp3"); loadSound("score", "sounds/score.mp3"); loadSound("metro", "sounds/metro.wav"); @@ -2956,6 +3122,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { loadSound("hitsoundCarterRedacted", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundMarc", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundRedVelvety", "sounds/hitsoundRedVelvety.wav"); + loadSound("hitsoundSonicAndTails", "sounds/hitsoundJellyBean.wav"); loadSound("hitsoundMarkyMark", "sounds/burp.mp3"); loadSprite("JellyBeanPre", "sprites/previews/JellyBeanPre.png"); loadSprite("RedVelvetyPre", "sprites/previews/RedVelvetyPre.png"); @@ -2963,6 +3130,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { loadSprite("CarterRedactedPre", "sprites/previews/CarterRedactedPre.png"); loadSprite("MarcPre", "sprites/previews/MarcPre.png"); loadSprite("FaithPlatePre", "sprites/previews/FaithPlatePre.png"); + loadSprite("SonicAndTailsPre", "sprites/previews/SonicAndTailsPre.png"); loadSprite("tutorialBG0", "sprites/bgCake.png"); loadSprite("tutorialFG0", "sprites/fgCake0.png"); loadSprite("tutorialFG1", "sprites/fgCake1.png"); @@ -3104,6 +3272,136 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } } }); + loadSprite("sonicBG0", "sprites/SonicBG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 6, + speed: 10, + loop: true + } + } + }); + loadSprite("sonicFG0", "sprites/SonicFG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 0, + speed: 20 + } + } + }); + loadSprite("SonicAndTailssonic0", "sprites/SonicMidSim.png", { + sliceX: 3, + sliceY: 4, + anims: { + idle: { + from: 0, + to: 6, + speed: 20 + }, + talk: { + from: 7, + to: 8, + speed: 20 + }, + miss: { + from: 9, + to: 11, + speed: 10 + } + } + }); + loadSprite("SonicAndTailssonic1", "sprites/TailsMidSim.png", { + sliceX: 3, + sliceY: 9, + anims: { + idle: { + from: 0, + to: 15, + speed: 20 + }, + talk: { + from: 16, + to: 23, + speed: 20 + }, + miss: { + from: 24, + to: 26, + speed: 10 + } + } + }); + loadSprite("greenBG0", "sprites/SonicBG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 6, + speed: 10, + loop: true + } + } + }); + loadSprite("greenFG0", "sprites/SonicFG.png", { + sliceX: 3, + sliceY: 3, + anims: { + idle: { + from: 0, + to: 0, + speed: 20 + } + } + }); + loadSprite("SonicAndTailsgreen0", "sprites/SonicMidSim.png", { + sliceX: 3, + sliceY: 4, + anims: { + idle: { + from: 0, + to: 6, + speed: 20 + }, + talk: { + from: 7, + to: 8, + speed: 20 + }, + miss: { + from: 9, + to: 11, + speed: 10 + } + } + }); + loadSprite("SonicAndTailsgreen1", "sprites/TailsMidSim.png", { + sliceX: 3, + sliceY: 9, + anims: { + idle: { + from: 0, + to: 15, + speed: 20 + }, + talk: { + from: 16, + to: 23, + speed: 20 + }, + miss: { + from: 24, + to: 26, + speed: 10 + } + } + }); resolve("All assets loaded."); })); var charts = { @@ -3118,6 +3416,18 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { 120, "................................................................................................................................J...J...J...JJJJJ...J...J...J...J.J.J.J.J.J.J.J.JJJJJJJJJ.J.J.JJJ.J.J.J.J.J.JJJJJ.JJJ.J.J.J.J.J.J.JJJ.JJJ.J.J.J.JJJJJJJJJJJJJJJJJ.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.J.JJJ.JJJ.JJJ.JJJ.JJJ.J.J.J.J.JJJ.J.JJJJJJJJJJJJJ.J.J.J.JJJJJ.J.J.J.J.J.J.J.JJJJJ.J.J.J.J.JJ.J..J.JJJ.JJJ.J.J.JJJ.JJJ.J.J.J.JJJJJ.J.J.JJJ.JJJJJJJJJJJJJJJJJ.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.JJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.J.J.JJJ.J.J.JJJ.J.J.J.J.J.J.JJJ.JJJJ.JJ.J.J.J.JJ..J...J.J.J.J.J.J.JJJJJJJJJJJJJ.J.J.J.J.J.J.J.J.J.JJJJJ.J.J.J.J.J.J.J.J...J...J.J.J.J.JJJJJJJJJ.J.J.J.J.J.J.JJJ.J.JJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.J.J.JJJJ.JJJJJJJJJJJJJJJ.JJJJJJJJJJJJJ..J.JJJ.JJJJJJJJJJ....J.J.JJJJJJJ.J.JJJ.JJJJJJJJJJJJJJJJJJJJJ.J.J.J.JJJ.JJJJJJJJJ....JJJJJJJJJ....JJJJJJJ.JJJJJJJ..JJJ..J.JJJJJJJJJJJJJJJ.JJJJJJJ..JJJ....JJJJJJJJJJJJJJJ.JJJJJJJ..JJJ....JJJJJJJJJJJJJJJ.JJJJJJJJ.JJJ..J.JJJJJJJ.J.JJJ.JJJJJJJJJJJJJ.JJJ.J...JJJ.J.JJJ.JJJJJJJJJJJJJ.JJJ.JJJ.JJJ.J.JJJJJJJJJJJJJJJJJJJJJ.JJJ.JJJ.J.JJJ.JJJJJJJJJJJJJJJJJ.JJJ.JJJ.J.JJJ.JJJJJJJJJJJJJJJ...JJJJ........J...JJJJ........J...JJJJ........J...JJJJ........J.J.JJJJ........JJJ.JJJ.JJJ.J.JJJ.J.JJJJ........JJJ.JJJ.JJJ.JJJJJ.J.JJJJ........JJJ.JJJ.JJJ.J.JJJ.J.JJJJ........JJJ.JJJ.JJJ.JJJJJ.J.JJJJJ.......JJJ.J.............", 4 + ], + "sonic": [ + 2, + 139, + "..................................................J.J.J.J.J.JJ....J.J.J.J..J........JJJJ..J.JJ.J..................T.T.T.T.T.TT....T...T.T..T........TTT.T.T.TT.T..................J.J.J.J.J.J.J.J.J...J...........J.J.J.J.J.JJ..J.................T.T.T.T.T.T.T.T.T...T...........T.T.T.T.T.TT..T...............J.....J.....J.J.J.JJ....T.TT........J.J.J.J.J.J.J.J...J.........T.....T.....T.T.T.TT....J.JJ....DD.D.............................................................", + 1 + ], + "green": [ + 2, + 150, + "................J.....J.....J.....J.....J...J...J.....J.....J...................J.....J.....J...J.....J.....J...J.....J.................................T.T...T.T...T.T...T...........T.T.T...T.T...T.T...T.............T.T...T.T...T.T...T...........T.T.T...T.T...T.T...T.....T.......J.J...J.J...D.J.T.J.T...T.....J.J.J...J.J...D.J.T.J.T...T.......J.J...J.J...D.J.T.J.T...T.....J.J.J...J.J...D.J.T.J.D...D.....J.....J.....J.....J.T.D.T.J.....J.....J.....J.....J.T.D.T.J.....J.....J.....J.....J.T.D.T.J.T.T.T...T.T.T...T.T.T.T.T.T.T.........J.J...J.J...J.J...J...........J.J.J...J.J...J.J...J.............J.J...J.J...J.J...J...........J.J.J...J.J...J.J...J.....J.......T.T...T.T...D.T.J.T.J...J.....T.T.T...T.T...D.T.J.T.J...J.......T.T...T.T...D.T.J.T.J...J.....T.T.T...T.T...D.T.J.T.J.T.D.....T.....T.....T.....T.J.D.J.T.....T.....T.....T.....T.J.D.J.T.....T.....T.....T.....T.J.D.J.T.J.J.J...J.J.J...J.J.J.J.J.J.J................................................................................................................................................................", + 1 ] }; var songIdx = 0; @@ -3163,18 +3473,39 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { "ui0", "ui1" ], "ui0"); - const player = add([ - sprite(char + song), - layer("JELLYBEAN"), - "dances", - song == "faith" ? pos(0, 0) : pos(width() / 2 - 162, height() - 400), - scale(chart[3]) - ]); + var player; + var player2; + if (song == "sonic" || song == "green") { + player = add([ + sprite(char + song + "0"), + layer("JELLYBEAN"), + "dances", + pos(20, 108), + scale(chart[3]) + ]); + player2 = add([ + sprite(char + song + "1"), + layer("JELLYBEAN"), + "dances", + pos(224, 20), + scale(chart[3]) + ]); + } else { + player = add([ + sprite(char + song), + layer("JELLYBEAN"), + "dances", + song == "faith" ? pos(0, 0) : pos(width() / 2 - 162, height() - 400), + scale(chart[3]) + ]); + } const bg = add([ sprite(song + "BG0"), layer("bg"), scale(chart[3]) ]); + if (song == "sonic" || song == "green") + bg.play("idle"); const fg = add([ sprite(song + "FG0"), song == "faith" ? layer("bg") : layer("fg"), @@ -3238,13 +3569,13 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { every("note", (j) => { j.pos.x = lerp(width(), strumLine, (underlay.time() - j.created) / chart[0]); if (autoplay) { - if (j.pos.x <= strumLine) { + if (j.pos.x <= strumLine && !j.empty) { play(hitsound); player.play("talk"); destroy(j); } } else { - if (j.pos.x <= strumLine - 20) { + if (j.pos.x <= strumLine - 20 && !j.empty) { score -= 200; destroy(j); play("explode"); @@ -3254,6 +3585,12 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { health -= 0.1; } } + if (j.pos.x >= strumLine - 20 && j.empty) { + if (j.pos.x <= strumLine) { + destroy(j); + player2 == null ? void 0 : player2.play("talk"); + } + } }); } }); @@ -3382,6 +3719,45 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { } ]); break; + case "T": + add([ + rect(0, 50), + pos(width(), 20), + "note" + prevStep, + "note", + "empty", + { + created: underlay.time(), + empty: true, + type: chart[2][Math.floor(prevStep)] + } + ]); + break; + case "D": + add([ + rect(10, 50), + pos(width(), 20), + chart[2][Math.floor(prevStep)] == "F" ? color(168, 56, 50) : color(232, 3, 252), + "note" + prevStep, + "note", + { + created: underlay.time(), + type: chart[2][Math.floor(prevStep)] + } + ]); + add([ + rect(0, 50), + pos(width(), 20), + "note" + prevStep, + "note", + "empty", + { + created: underlay.time(), + empty: true, + type: chart[2][Math.floor(prevStep)] + } + ]); + break; } } __name(makeNote, "makeNote"); @@ -3389,30 +3765,59 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { var iv = false; every("note", (j) => { if (!iv) { - if (j.pos.x >= strumLine - 20) { - if (j.pos.x <= strumLine + 22) { - iv = true; - destroy(j); - noteClick.play("click"); - combo += 1; + if (!j.empty) { + var str = "No Rating"; + var theColor = WHITE; + if (j.pos.x >= strumLine - 20) { + if (j.pos.x <= strumLine + 22) { + iv = true; + destroy(j); + noteClick.play("click"); + combo += 1; + str = "MID"; + theColor = RED; + } + if (j.pos.x <= strumLine + 12) { + play(hitsound); + player.play("talk"); + score += 20; + health += 0.01; + str = "Perfect!"; + theColor = MAGENTA; + } + if (j.pos.x <= strumLine) { + score += 50; + health += 0.02; + str = "Marvelous!"; + theColor = YELLOW; + } + if (j.pos.x <= strumLine - 3) { + score += 50; + health -= 0.01; + str = "Perfect!"; + theColor = MAGENTA; + } + if (j.pos.x <= strumLine - 8) { + score -= 100; + health -= 0.02; + str = "Overshot"; + theColor = CYAN; + } } - if (j.pos.x <= strumLine + 12) { - play(hitsound); - player.play("talk"); - score += 20; - health += 0.01; - } - if (j.pos.x <= strumLine) { - score += 50; - health += 0.02; - } - if (j.pos.x <= strumLine - 3) { - score += 50; - health -= 0.01; - } - if (j.pos.x <= strumLine - 8) { - score -= 100; - health -= 0.02; + if (str != "No Rating") { + var origpos = strumLine - 16; + var ratingtxt = add([ + text(str, { + size: 30 + }), + pos(origpos, 48), + color(theColor), + origin("right") + ]); + tween(ratingtxt.pos, ["y"], 5, 48, 300, easings.easeOutSine, tweentypes.NORMAL); + tween(ratingtxt, ["opacity"], 5, 1, 0, easings.easeOutSine, tweentypes.NORMAL, function() { + destroy(ratingtxt); + }); } } } @@ -3431,7 +3836,15 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { scene("Help", () => { var songs = [ "tutorial", - "faith" + "faith", + "green", + "sonic" + ]; + var names = [ + "Tutorial", + "Friendly Faith Plate", + "Green Hill Zone", + "Emerald Hill Zone" ]; var chars = { tutorial: [ @@ -3443,6 +3856,12 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { ], faith: [ "FaithPlate" + ], + green: [ + "SonicAndTails" + ], + sonic: [ + "SonicAndTails" ] }; const bg = add([ @@ -3494,7 +3913,7 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { origin: "top" }); drawText({ - text: "SONG (" + (songIdx + 1) + "/" + songs.length + "): " + songs[songIdx].toUpperCase(), + text: "SONG (" + (songIdx + 1) + "/" + songs.length + "): " + names[songIdx].toUpperCase(), size: 30, pos: vec2(width() / 2, 160), origin: "top" @@ -3577,10 +3996,10 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { changeIdx(1); }); onKeyPress("down", () => { - changeSongIdx(-1); + changeSongIdx(1); }); onKeyPress("up", () => { - changeSongIdx(1); + changeSongIdx(-1); }); onKeyPress("`", () => { go("Chart", songs[songIdx]); @@ -3592,16 +4011,16 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { changeIdx(1); }); onClick("DownText", () => { - changeSongIdx(-1); + changeSongIdx(1); }); onClick("UpText", () => { - changeSongIdx(1); + changeSongIdx(-1); }); onKeyPress("space", () => { go("Game", [songs[songIdx], chars[songs[songIdx]][idx]]); }); onClick("TEXT TEXT", () => { - go("Game", ["tutorial", chars[songs[songIdx]][idx]]); + go("Game", [songs[songIdx], chars[songs[songIdx]][idx]]); }); }); scene("Title", () => { @@ -3727,9 +4146,9 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { ]); var theP = add([ pos(width() * 0.6, height() * 0.8), - rect(60, 60), + rect(60, 30), text("P", { - size: 48, + size: 24, width: 60 }), color(255, 255, 255), @@ -3738,6 +4157,19 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { area(), "theP" ]); + var theD = add([ + pos(width() * 0.6, height() * 0.8 + 30), + rect(60, 30), + text("D", { + size: 24, + width: 60 + }), + color(255, 255, 255), + outline(4, WHITE), + origin("center"), + area(), + "theD" + ]); var consoleButton = add([ pos(width() * 0.8, height() * 0.8), rect(60, 60), @@ -3759,6 +4191,9 @@ vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) { onClick("theP", (o) => { tool = "P"; }); + onClick("theD", (o) => { + tool = "D"; + }); onClick("consoleButton", (o) => { console.log(tempChart.join("")); }); diff --git a/dist/game.js.map b/dist/game.js.map index accb096..1798cea 100644 --- a/dist/game.js.map +++ b/dist/game.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../node_modules/kaboom/src/utils.ts", "../node_modules/kaboom/src/math.ts", "../node_modules/kaboom/src/fps.ts", "../node_modules/kaboom/src/timer.ts", "../node_modules/kaboom/src/kaboom.ts", "../code/main.js"], - "sourcesContent": ["export class IDList extends Map {\n\t_lastID: number;\n\tconstructor(...args) {\n\t\tsuper(...args);\n\t\tthis._lastID = 0;\n\t}\n\tpush(v: T): number {\n\t\tconst id = this._lastID;\n\t\tthis.set(id, v);\n\t\tthis._lastID++;\n\t\treturn id;\n\t}\n\tpushd(v: T): () => void {\n\t\tconst id = this.push(v);\n\t\treturn () => this.delete(id);\n\t}\n}\n\nexport function deepEq(o1: any, o2: any): boolean {\n\tconst t1 = typeof o1;\n\tconst t2 = typeof o2;\n\tif (t1 !== t2) {\n\t\treturn false;\n\t}\n\tif (t1 === \"object\" && t2 === \"object\") {\n\t\tconst k1 = Object.keys(o1);\n\t\tconst k2 = Object.keys(o2);\n\t\tif (k1.length !== k2.length) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (const k of k1) {\n\t\t\tconst v1 = o1[k];\n\t\t\tconst v2 = o2[k];\n\t\t\tif (!(typeof v1 === \"function\" && typeof v2 === \"function\")) {\n\t\t\t\tif (!deepEq(v1, v2)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\treturn o1 === o2;\n}\n\nexport function downloadURL(url: string, filename: string) {\n\tconst a = document.createElement(\"a\");\n\tdocument.body.appendChild(a);\n\ta.setAttribute(\"style\", \"display: none\");\n\ta.href = url;\n\ta.download = filename;\n\ta.click();\n\tdocument.body.removeChild(a);\n}\n\nexport function downloadBlob(blob: Blob, filename: string) {\n\tconst url = URL.createObjectURL(blob);\n\tdownloadURL(url, filename);\n\tURL.revokeObjectURL(url);\n}\n\nexport function isDataURL(str: string) {\n\treturn str.match(/^data:\\w+\\/\\w+;base64,.+/);\n}\n\nexport const uid = (() => {\n\tlet id = 0;\n\treturn () => id++;\n})();\n\nconst warned = new Set();\n\nexport function deprecateMsg(oldName: string, newName: string) {\n\tif (!warned.has(oldName)) {\n\t\twarned.add(oldName);\n\t\tconsole.warn(`${oldName} is deprecated. Use ${newName} instead.`);\n\t}\n}\n\nexport const deprecate = (oldName: string, newName: string, newFunc: (...args) => any) => (...args) => {\n\tdeprecateMsg(oldName, newName);\n\treturn newFunc(...args);\n};\n", "import {\n\tVec4,\n\tPoint,\n\tPolygon,\n\tArea,\n} from \"./types\";\n\nimport {\n\tdeprecateMsg,\n} from \"./utils\";\n\nexport function deg2rad(deg: number): number {\n\treturn deg * Math.PI / 180;\n}\n\nexport function rad2deg(rad: number): number {\n\treturn rad * 180 / Math.PI;\n}\n\nexport function clamp(\n\tval: number,\n\tmin: number,\n\tmax: number,\n): number {\n\tif (min > max) {\n\t\treturn clamp(val, max, min);\n\t}\n\treturn Math.min(Math.max(val, min), max);\n}\n\nexport function lerp(\n\ta: number,\n\tb: number,\n\tt: number,\n): number {\n\treturn a + (b - a) * t;\n}\n\nexport function map(\n\tv: number,\n\tl1: number,\n\th1: number,\n\tl2: number,\n\th2: number,\n): number {\n\treturn l2 + (v - l1) / (h1 - l1) * (h2 - l2);\n}\n\nexport function mapc(\n\tv: number,\n\tl1: number,\n\th1: number,\n\tl2: number,\n\th2: number,\n): number {\n\treturn clamp(map(v, l1, h1, l2, h2), l2, h2);\n}\n\nexport class Vec2 {\n\tx: number = 0;\n\ty: number = 0;\n\tconstructor(x: number = 0, y: number = x) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\tstatic fromAngle(deg: number) {\n\t\tconst angle = deg2rad(deg);\n\t\treturn new Vec2(Math.cos(angle), Math.sin(angle));\n\t}\n\tstatic LEFT = new Vec2(-1, 0);\n\tstatic RIGHT = new Vec2(1, 0);\n\tstatic UP = new Vec2(0, -1);\n\tstatic DOWN = new Vec2(0, 1);\n\tclone(): Vec2 {\n\t\treturn new Vec2(this.x, this.y);\n\t}\n\tadd(...args): Vec2 {\n\t\tconst p2 = vec2(...args);\n\t\treturn new Vec2(this.x + p2.x, this.y + p2.y);\n\t}\n\tsub(...args): Vec2 {\n\t\tconst p2 = vec2(...args);\n\t\treturn new Vec2(this.x - p2.x, this.y - p2.y);\n\t}\n\tscale(...args): Vec2 {\n\t\tconst s = vec2(...args);\n\t\treturn new Vec2(this.x * s.x, this.y * s.y);\n\t}\n\tdist(...args): number {\n\t\tconst p2 = vec2(...args);\n\t\treturn Math.sqrt(\n\t\t\t(this.x - p2.x) * (this.x - p2.x)\n\t\t\t+ (this.y - p2.y) * (this.y - p2.y)\n\t\t);\n\t}\n\tlen(): number {\n\t\treturn this.dist(new Vec2(0, 0));\n\t}\n\tunit(): Vec2 {\n\t\treturn this.scale(1 / this.len());\n\t}\n\tnormal(): Vec2 {\n\t\treturn new Vec2(this.y, -this.x);\n\t}\n\tdot(p2: Vec2): number {\n\t\treturn this.x * p2.x + this.y * p2.y;\n\t}\n\tangle(...args): number {\n\t\tconst p2 = vec2(...args);\n\t\treturn rad2deg(Math.atan2(this.y - p2.y, this.x - p2.x));\n\t}\n\tlerp(p2: Vec2, t: number): Vec2 {\n\t\treturn new Vec2(lerp(this.x, p2.x, t), lerp(this.y, p2.y, t));\n\t}\n\ttoFixed(n: number): Vec2 {\n\t\treturn new Vec2(Number(this.x.toFixed(n)), Number(this.y.toFixed(n)));\n\t}\n\teq(other: Vec2): boolean {\n\t\treturn this.x === other.x && this.y === other.y;\n\t}\n\ttoString(): string {\n\t\treturn `(${this.x.toFixed(2)}, ${this.y.toFixed(2)})`;\n\t}\n\tstr(): string {\n\t\treturn this.toString();\n\t}\n}\n\nexport function vec2(...args): Vec2 {\n\tif (args.length === 1) {\n\t\tif (args[0] instanceof Vec2) {\n\t\t\treturn vec2(args[0].x, args[0].y);\n\t\t} else if (Array.isArray(args[0]) && args[0].length === 2) {\n\t\t\treturn vec2.apply(null, args[0]);\n\t\t}\n\t}\n\treturn new Vec2(...args);\n}\n\nexport class Vec3 {\n\tx: number = 0;\n\ty: number = 0;\n\tz: number = 0;\n\tconstructor(x: number, y: number, z: number) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t\tthis.z = z;\n\t}\n\txy() {\n\t\treturn vec2(this.x, this.y);\n\t}\n}\n\nexport const vec3 = (x, y, z) => new Vec3(x, y, z);\n\nexport class Color {\n\n\tr: number = 255;\n\tg: number = 255;\n\tb: number = 255;\n\n\tconstructor(r: number, g: number, b: number) {\n\t\tthis.r = clamp(r, 0, 255);\n\t\tthis.g = clamp(g, 0, 255);\n\t\tthis.b = clamp(b, 0, 255);\n\t}\n\n\tstatic fromArray(arr: number[]) {\n\t\treturn new Color(arr[0], arr[1], arr[2])\n\t}\n\n\tstatic RED = rgb(255, 0, 0);\n\tstatic GREEN = rgb(0, 255, 0);\n\tstatic BLUE = rgb(0, 0, 255);\n\tstatic YELLOW = rgb(255, 255, 0);\n\tstatic MAGENTA = rgb(255, 0, 255);\n\tstatic CYAN = rgb(0, 255, 255);\n\tstatic WHITE = rgb(255, 255, 255);\n\tstatic BLACK = rgb(0, 0, 0);\n\n\tclone(): Color {\n\t\treturn new Color(this.r, this.g, this.b);\n\t}\n\n\tlighten(a: number): Color {\n\t\treturn new Color(this.r + a, this.g + a, this.b + a);\n\t}\n\n\tdarken(a: number): Color {\n\t\treturn this.lighten(-a);\n\t}\n\n\tinvert(): Color {\n\t\treturn new Color(255 - this.r, 255 - this.g, 255 - this.b);\n\t}\n\n\tmult(other: Color): Color {\n\t\treturn new Color(\n\t\t\tthis.r * other.r / 255,\n\t\t\tthis.g * other.g / 255,\n\t\t\tthis.b * other.b / 255,\n\t\t);\n\t}\n\n\teq(other: Color): boolean {\n\t\treturn this.r === other.r\n\t\t\t&& this.g === other.g\n\t\t\t&& this.b === other.b\n\t\t\t;\n\t}\n\n\tstr(): string {\n\t\tdeprecateMsg(\"str()\", \"toString()\");\n\t\treturn `(${this.r}, ${this.g}, ${this.b})`;\n\t}\n\n\ttoString(): string {\n\t\treturn `(${this.r}, ${this.g}, ${this.b})`;\n\t}\n\n\tstatic fromHSL(h: number, s: number, l: number) {\n\n\t\tif (s == 0){\n\t\t\treturn rgb(255 * l, 255 * l, 255 * l);\n\t\t}\n\n\t\tconst hue2rgb = (p, q, t) => {\n\t\t\tif (t < 0) t += 1;\n\t\t\tif (t > 1) t -= 1;\n\t\t\tif (t < 1 / 6) return p + (q - p) * 6 * t;\n\t\t\tif (t < 1 / 2) return q;\n\t\t\tif (t < 2 / 3) return p + (q - p) * (2/3 - t) * 6;\n\t\t\treturn p;\n\t\t}\n\n\t\tconst q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n\t\tconst p = 2 * l - q;\n\t\tconst r = hue2rgb(p, q, h + 1 / 3);\n\t\tconst g = hue2rgb(p, q, h);\n\t\tconst b = hue2rgb(p, q, h - 1 / 3);\n\n\t\treturn new Color(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));\n\n\t}\n\n}\n\nexport function rgb(...args): Color {\n\tif (args.length === 0) {\n\t\treturn new Color(255, 255, 255);\n\t} else if (args.length === 1) {\n\t\tif (args[0] instanceof Color) {\n\t\t\treturn args[0].clone();\n\t\t} else if (Array.isArray(args[0]) && args[0].length === 3) {\n\t\t\treturn Color.fromArray(args[0]);\n\t\t}\n\t}\n\t// @ts-ignore\n\treturn new Color(...args);\n}\n\nexport const hsl2rgb = (h, s, l) => Color.fromHSL(h, s, l);\n\nexport class Quad {\n\tx: number = 0;\n\ty: number = 0;\n\tw: number = 1;\n\th: number = 1;\n\tconstructor(x: number, y: number, w: number, h: number) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t\tthis.w = w;\n\t\tthis.h = h;\n\t}\n\tscale(other: Quad): Quad {\n\t\treturn new Quad(\n\t\t\tthis.x + this.w * other.x,\n\t\t\tthis.y + this.h * other.y,\n\t\t\tthis.w * other.w,\n\t\t\tthis.h * other.h\n\t\t);\n\t}\n\tclone(): Quad {\n\t\treturn new Quad(this.x, this.y, this.w, this.h);\n\t}\n\teq(other: Quad): boolean {\n\t\treturn this.x === other.x\n\t\t\t&& this.y === other.y\n\t\t\t&& this.w === other.w\n\t\t\t&& this.h === other.h;\n\t}\n\ttoString(): string {\n\t\treturn `quad(${this.x}, ${this.y}, ${this.w}, ${this.h})`;\n\t}\n}\n\nexport function quad(x: number, y: number, w: number, h: number): Quad {\n\treturn new Quad(x, y, w, h);\n}\n\nexport class Mat4 {\n\n\tm: number[] = [\n\t\t1, 0, 0, 0,\n\t\t0, 1, 0, 0,\n\t\t0, 0, 1, 0,\n\t\t0, 0, 0, 1,\n\t];\n\n\tconstructor(m?: number[]) {\n\t\tif (m) {\n\t\t\tthis.m = m;\n\t\t}\n\t}\n\n\tclone(): Mat4 {\n\t\treturn new Mat4(this.m);\n\t};\n\n\tmult(other: Mat4): Mat4 {\n\n\t\tconst out = [];\n\n\t\tfor (let i = 0; i < 4; i++) {\n\t\t\tfor (let j = 0; j < 4; j++) {\n\t\t\t\tout[i * 4 + j] =\n\t\t\t\t\tthis.m[0 * 4 + j] * other.m[i * 4 + 0] +\n\t\t\t\t\tthis.m[1 * 4 + j] * other.m[i * 4 + 1] +\n\t\t\t\t\tthis.m[2 * 4 + j] * other.m[i * 4 + 2] +\n\t\t\t\t\tthis.m[3 * 4 + j] * other.m[i * 4 + 3];\n\t\t\t}\n\t\t}\n\n\t\treturn new Mat4(out);\n\n\t}\n\n\tmultVec4(p: Vec4): Vec4 {\n\t\treturn {\n\t\t\tx: p.x * this.m[0] + p.y * this.m[4] + p.z * this.m[8] + p.w * this.m[12],\n\t\t\ty: p.x * this.m[1] + p.y * this.m[5] + p.z * this.m[9] + p.w * this.m[13],\n\t\t\tz: p.x * this.m[2] + p.y * this.m[6] + p.z * this.m[10] + p.w * this.m[14],\n\t\t\tw: p.x * this.m[3] + p.y * this.m[7] + p.z * this.m[11] + p.w * this.m[15]\n\t\t};\n\t}\n\n\tmultVec3(p: Vec3): Vec3 {\n\t\tconst p4 = this.multVec4({\n\t\t\tx: p.x,\n\t\t\ty: p.y,\n\t\t\tz: p.z,\n\t\t\tw: 1.0,\n\t\t});\n\t\treturn vec3(p4.x, p4.y, p4.z);\n\t}\n\n\tmultVec2(p: Vec2): Vec2 {\n\t\treturn vec2(\n\t\t\tp.x * this.m[0] + p.y * this.m[4] + 0 * this.m[8] + 1 * this.m[12],\n\t\t\tp.x * this.m[1] + p.y * this.m[5] + 0 * this.m[9] + 1 * this.m[13],\n\t\t);\n\t}\n\n\tstatic translate(p: Vec2): Mat4 {\n\t\treturn new Mat4([\n\t\t\t1, 0, 0, 0,\n\t\t\t0, 1, 0, 0,\n\t\t\t0, 0, 1, 0,\n\t\t\tp.x, p.y, 0, 1,\n\t\t]);\n\t}\n\n\tstatic scale(s: Vec2): Mat4 {\n\t\treturn new Mat4([\n\t\t\ts.x, 0, 0, 0,\n\t\t\t0, s.y, 0, 0,\n\t\t\t0, 0, 1, 0,\n\t\t\t0, 0, 0, 1,\n\t\t]);\n\t}\n\n\tstatic rotateX(a: number): Mat4 {\n\t\ta = deg2rad(-a);\n\t\treturn new Mat4([\n\t\t\t1, 0, 0, 0,\n\t\t\t0, Math.cos(a), -Math.sin(a), 0,\n\t\t\t0, Math.sin(a), Math.cos(a), 0,\n\t\t\t0, 0, 0, 1,\n\t\t]);\n\t}\n\n\tstatic rotateY(a: number): Mat4 {\n\t\ta = deg2rad(-a);\n\t\treturn new Mat4([\n\t\t\tMath.cos(a), 0, Math.sin(a), 0,\n\t\t\t0, 1, 0, 0,\n\t\t\t-Math.sin(a), 0, Math.cos(a), 0,\n\t\t\t0, 0, 0, 1,\n\t\t]);\n\t}\n\n\tstatic rotateZ(a: number): Mat4 {\n\t\ta = deg2rad(-a);\n\t\treturn new Mat4([\n\t\t\tMath.cos(a), -Math.sin(a), 0, 0,\n\t\t\tMath.sin(a), Math.cos(a), 0, 0,\n\t\t\t0, 0, 1, 0,\n\t\t\t0, 0, 0, 1,\n\t\t]);\n\t}\n\n\ttranslate(p: Vec2): Mat4 {\n\t\treturn this.mult(Mat4.translate(p));\n\t}\n\n\tscale(s: Vec2): Mat4 {\n\t\treturn this.mult(Mat4.scale(s));\n\t}\n\n\trotateX(a: number): Mat4 {\n\t\treturn this.mult(Mat4.rotateX(a));\n\t}\n\n\trotateY(a: number): Mat4 {\n\t\treturn this.mult(Mat4.rotateY(a));\n\t}\n\n\trotateZ(a: number): Mat4 {\n\t\treturn this.mult(Mat4.rotateZ(a));\n\t}\n\n\tinvert(): Mat4 {\n\n\t\tconst out = [];\n\n\t\tconst f00 = this.m[10] * this.m[15] - this.m[14] * this.m[11];\n\t\tconst f01 = this.m[9] * this.m[15] - this.m[13] * this.m[11];\n\t\tconst f02 = this.m[9] * this.m[14] - this.m[13] * this.m[10];\n\t\tconst f03 = this.m[8] * this.m[15] - this.m[12] * this.m[11];\n\t\tconst f04 = this.m[8] * this.m[14] - this.m[12] * this.m[10];\n\t\tconst f05 = this.m[8] * this.m[13] - this.m[12] * this.m[9];\n\t\tconst f06 = this.m[6] * this.m[15] - this.m[14] * this.m[7];\n\t\tconst f07 = this.m[5] * this.m[15] - this.m[13] * this.m[7];\n\t\tconst f08 = this.m[5] * this.m[14] - this.m[13] * this.m[6];\n\t\tconst f09 = this.m[4] * this.m[15] - this.m[12] * this.m[7];\n\t\tconst f10 = this.m[4] * this.m[14] - this.m[12] * this.m[6];\n\t\tconst f11 = this.m[5] * this.m[15] - this.m[13] * this.m[7];\n\t\tconst f12 = this.m[4] * this.m[13] - this.m[12] * this.m[5];\n\t\tconst f13 = this.m[6] * this.m[11] - this.m[10] * this.m[7];\n\t\tconst f14 = this.m[5] * this.m[11] - this.m[9] * this.m[7];\n\t\tconst f15 = this.m[5] * this.m[10] - this.m[9] * this.m[6];\n\t\tconst f16 = this.m[4] * this.m[11] - this.m[8] * this.m[7];\n\t\tconst f17 = this.m[4] * this.m[10] - this.m[8] * this.m[6];\n\t\tconst f18 = this.m[4] * this.m[9] - this.m[8] * this.m[5];\n\n\t\tout[0] = this.m[5] * f00 - this.m[6] * f01 + this.m[7] * f02;\n\t\tout[4] = -(this.m[4] * f00 - this.m[6] * f03 + this.m[7] * f04);\n\t\tout[8] = this.m[4] * f01 - this.m[5] * f03 + this.m[7] * f05;\n\t\tout[12] = -(this.m[4] * f02 - this.m[5] * f04 + this.m[6] * f05);\n\n\t\tout[1] = -(this.m[1] * f00 - this.m[2] * f01 + this.m[3] * f02);\n\t\tout[5] = this.m[0] * f00 - this.m[2] * f03 + this.m[3] * f04;\n\t\tout[9] = -(this.m[0] * f01 - this.m[1] * f03 + this.m[3] * f05);\n\t\tout[13] = this.m[0] * f02 - this.m[1] * f04 + this.m[2] * f05;\n\n\t\tout[2] = this.m[1] * f06 - this.m[2] * f07 + this.m[3] * f08;\n\t\tout[6] = -(this.m[0] * f06 - this.m[2] * f09 + this.m[3] * f10);\n\t\tout[10] = this.m[0] * f11 - this.m[1] * f09 + this.m[3] * f12;\n\t\tout[14] = -(this.m[0] * f08 - this.m[1] * f10 + this.m[2] * f12);\n\n\t\tout[3] = -(this.m[1] * f13 - this.m[2] * f14 + this.m[3] * f15);\n\t\tout[7] = this.m[0] * f13 - this.m[2] * f16 + this.m[3] * f17;\n\t\tout[11] = -(this.m[0] * f14 - this.m[1] * f16 + this.m[3] * f18);\n\t\tout[15] = this.m[0] * f15 - this.m[1] * f17 + this.m[2] * f18;\n\n\t\tconst det =\n\t\t\tthis.m[0] * out[0] +\n\t\t\tthis.m[1] * out[4] +\n\t\t\tthis.m[2] * out[8] +\n\t\t\tthis.m[3] * out[12];\n\n\t\tfor (let i = 0; i < 4; i++) {\n\t\t\tfor (let j = 0; j < 4; j++) {\n\t\t\t\tout[i * 4 + j] *= (1.0 / det);\n\t\t\t}\n\t\t}\n\n\t\treturn new Mat4(out);\n\n\t}\n\n\ttoString(): string {\n\t\treturn this.m.toString();\n\t}\n\n}\n\nexport function wave(lo: number, hi: number, t: number, f = Math.sin): number {\n\treturn lo + (f(t) + 1) / 2 * (hi - lo);\n}\n\n// basic ANSI C LCG\nconst A = 1103515245;\nconst C = 12345;\nconst M = 2147483648;\n\nexport class RNG {\n\tseed: number;\n\tconstructor(seed: number) {\n\t\tthis.seed = seed;\n\t}\n\tgen(...args) {\n\t\tif (args.length === 0) {\n\t\t\tthis.seed = (A * this.seed + C) % M;\n\t\t\treturn this.seed / M;\n\t\t} else if (args.length === 1) {\n\t\t\tif (typeof args[0] === \"number\") {\n\t\t\t\treturn this.gen(0, args[0]);\n\t\t\t} else if (args[0] instanceof Vec2) {\n\t\t\t\treturn this.gen(vec2(0, 0), args[0]);\n\t\t\t} else if (args[0] instanceof Color) {\n\t\t\t\treturn this.gen(rgb(0, 0, 0), args[0]);\n\t\t\t}\n\t\t} else if (args.length === 2) {\n\t\t\tif (typeof args[0] === \"number\" && typeof args[1] === \"number\") {\n\t\t\t\treturn (this.gen() * (args[1] - args[0])) + args[0];\n\t\t\t} else if (args[0] instanceof Vec2 && args[1] instanceof Vec2) {\n\t\t\t\treturn vec2(\n\t\t\t\t\tthis.gen(args[0].x, args[1].x),\n\t\t\t\t\tthis.gen(args[0].y, args[1].y),\n\t\t\t\t);\n\t\t\t} else if (args[0] instanceof Color && args[1] instanceof Color) {\n\t\t\t\treturn rgb(\n\t\t\t\t\tthis.gen(args[0].r, args[1].r),\n\t\t\t\t\tthis.gen(args[0].g, args[1].g),\n\t\t\t\t\tthis.gen(args[0].b, args[1].b),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// TODO: let user pass seed\nconst defRNG = new RNG(Date.now());\n\nexport function rng(seed: number): RNG {\n\tdeprecateMsg(\"rng()\", \"new RNG()\");\n\treturn new RNG(seed);\n}\n\nexport function randSeed(seed?: number): number {\n\tif (seed != null) {\n\t\tdefRNG.seed = seed;\n\t}\n\treturn defRNG.seed;\n}\n\nexport function rand(...args) {\n\t// @ts-ignore\n\treturn defRNG.gen(...args);\n}\n\n// TODO: randi() to return 0 / 1?\nexport function randi(...args) {\n\treturn Math.floor(rand(...args));\n}\n\nexport function chance(p: number): boolean {\n\treturn rand() <= p;\n}\n\nexport function choose(list: T[]): T {\n\treturn list[randi(list.length)];\n}\n\n// TODO: better name\nexport function testRectRect2(r1: Rect, r2: Rect): boolean {\n\treturn r1.p2.x >= r2.p1.x\n\t\t&& r1.p1.x <= r2.p2.x\n\t\t&& r1.p2.y >= r2.p1.y\n\t\t&& r1.p1.y <= r2.p2.y;\n}\n\nexport function testRectRect(r1: Rect, r2: Rect): boolean {\n\treturn r1.p2.x > r2.p1.x\n\t\t&& r1.p1.x < r2.p2.x\n\t\t&& r1.p2.y > r2.p1.y\n\t\t&& r1.p1.y < r2.p2.y;\n}\n\n// TODO: better name\nexport function testLineLineT(l1: Line, l2: Line): number | null {\n\n\tif ((l1.p1.x === l1.p2.x && l1.p1.y === l1.p2.y) || (l2.p1.x === l2.p2.x && l2.p1.y === l2.p2.y)) {\n\t\treturn null;\n\t}\n\n\tconst denom = ((l2.p2.y - l2.p1.y) * (l1.p2.x - l1.p1.x) - (l2.p2.x - l2.p1.x) * (l1.p2.y - l1.p1.y));\n\n\t// parallel\n\tif (denom === 0) {\n\t\treturn null;\n\t}\n\n\tconst ua = ((l2.p2.x - l2.p1.x) * (l1.p1.y - l2.p1.y) - (l2.p2.y - l2.p1.y) * (l1.p1.x - l2.p1.x)) / denom;\n\tconst ub = ((l1.p2.x - l1.p1.x) * (l1.p1.y - l2.p1.y) - (l1.p2.y - l1.p1.y) * (l1.p1.x - l2.p1.x)) / denom;\n\n\t// is the intersection on the segments\n\tif (ua < 0 || ua > 1 || ub < 0 || ub > 1) {\n\t\treturn null;\n\t}\n\n\treturn ua;\n\n}\n\nexport function testLineLine(l1: Line, l2: Line): Vec2 | null {\n\tconst t = testLineLineT(l1, l2);\n\tif (!t) return null;\n\treturn vec2(\n\t\tl1.p1.x + t * (l1.p2.x - l1.p1.x),\n\t\tl1.p1.y + t * (l1.p2.y - l1.p1.y),\n\t);\n}\n\nexport function testRectLine(r: Rect, l: Line): boolean {\n\tif (testRectPoint(r, l.p1) || testRectPoint(r, l.p2)) {\n\t\treturn true;\n\t}\n\treturn !!testLineLine(l, new Line(r.p1, vec2(r.p2.x, r.p1.y)))\n\t\t|| !!testLineLine(l, new Line(vec2(r.p2.x, r.p1.y), r.p2))\n\t\t|| !!testLineLine(l, new Line(r.p2, vec2(r.p1.x, r.p2.y)))\n\t\t|| !!testLineLine(l, new Line(vec2(r.p1.x, r.p2.y), r.p1));\n}\n\nexport function testRectPoint2(r: Rect, pt: Point): boolean {\n\treturn pt.x >= r.p1.x && pt.x <= r.p2.x && pt.y >= r.p1.y && pt.y <= r.p2.y;\n}\n\nexport function testRectPoint(r: Rect, pt: Point): boolean {\n\treturn pt.x > r.p1.x && pt.x < r.p2.x && pt.y > r.p1.y && pt.y < r.p2.y;\n}\n\nexport function testRectCircle(r: Rect, c: Circle): boolean {\n\tconst nx = Math.max(r.p1.x, Math.min(c.center.x, r.p2.x));\n\tconst ny = Math.max(r.p1.y, Math.min(c.center.y, r.p2.y));\n\tconst nearestPoint = vec2(nx, ny);\n\treturn nearestPoint.dist(c.center) <= c.radius;\n}\n\nexport function testRectPolygon(r: Rect, p: Polygon): boolean {\n\treturn testPolygonPolygon(p, [\n\t\tr.p1,\n\t\tvec2(r.p2.x, r.p1.y),\n\t\tr.p2,\n\t\tvec2(r.p1.x, r.p2.y),\n\t]);\n}\n\n// TODO\nexport function testLinePoint(l: Line, pt: Vec2): boolean {\n\treturn false;\n}\n\n// TODO\nexport function testLineCircle(l: Line, c: Circle): boolean {\n\treturn false;\n}\n\nexport function testLinePolygon(l: Line, p: Polygon): boolean {\n\n\t// test if line is inside\n\tif (testPolygonPoint(p, l.p1) || testPolygonPoint(p, l.p2)) {\n\t\treturn true;\n\t}\n\n\t// test each line\n\tfor (let i = 0; i < p.length; i++) {\n\t\tconst p1 = p[i];\n\t\tconst p2 = p[(i + 1) % p.length];\n\t\tif (testLineLine(l, { p1, p2 })) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n\n}\n\nexport function testCirclePoint(c: Circle, p: Point): boolean {\n\treturn c.center.dist(p) < c.radius;\n}\n\nexport function testCircleCircle(c1: Circle, c2: Circle): boolean {\n\treturn c1.center.dist(c2.center) < c1.radius + c2.radius;\n}\n\n// TODO\nexport function testCirclePolygon(c: Circle, p: Polygon): boolean {\n\treturn false;\n}\n\nexport function testPolygonPolygon(p1: Polygon, p2: Polygon): boolean {\n\tfor (let i = 0; i < p1.length; i++) {\n\t\tconst l = {\n\t\t\tp1: p1[i],\n\t\t\tp2: p1[(i + 1) % p1.length],\n\t\t};\n\t\tif (testLinePolygon(l, p2)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html\nexport function testPolygonPoint(p: Polygon, pt: Point): boolean {\n\n\tlet c = false;\n\n\tfor (let i = 0, j = p.length - 1; i < p.length; j = i++) {\n\t\tif (\n\t\t\t((p[i].y > pt.y) != (p[j].y > pt.y))\n\t\t\t&& (pt.x < (p[j].x - p[i].x) * (pt.y - p[i].y) / (p[j].y - p[i].y) + p[i].x)\n\t\t) {\n\t\t\tc = !c;\n\t\t}\n\t}\n\n\treturn c;\n\n}\n\nexport function testPointPoint(p1: Point, p2: Point): boolean {\n\treturn p1.eq(p2);\n}\n\nexport function testAreaRect(a: Area, r: Rect): boolean {\n\tswitch (a.shape) {\n\t\tcase \"rect\": return testRectRect(r, a);\n\t\tcase \"line\": return testRectLine(r, a);\n\t\tcase \"circle\": return testRectCircle(r, a);\n\t\tcase \"polygon\": return testRectPolygon(r, a.pts);\n\t\tcase \"point\": return testRectPoint(r, a.pt);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a as Area).shape}`);\n}\n\nexport function testAreaLine(a: Area, l: Line): boolean {\n\tswitch (a.shape) {\n\t\tcase \"rect\": return testRectLine(a, l);\n\t\tcase \"line\": return Boolean(testLineLine(a, l));\n\t\tcase \"circle\": return testLineCircle(l, a);\n\t\tcase \"polygon\": return testLinePolygon(l, a.pts);\n\t\tcase \"point\": return testLinePoint(l, a.pt);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a as Area).shape}`);\n}\n\nexport function testAreaCircle(a: Area, c: Circle): boolean {\n\tswitch (a.shape) {\n\t\tcase \"rect\": return testRectCircle(a, c);\n\t\tcase \"line\": return testLineCircle(a, c);\n\t\tcase \"circle\": return testCircleCircle(a, c);\n\t\tcase \"polygon\": return testCirclePolygon(c, a.pts);\n\t\tcase \"point\": return testCirclePoint(c, a.pt);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a as Area).shape}`);\n}\n\nexport function testAreaPolygon(a: Area, p: Polygon): boolean {\n\tswitch (a.shape) {\n\t\tcase \"rect\": return testRectPolygon(a, p);\n\t\tcase \"line\": return testLinePolygon(a, p);\n\t\tcase \"circle\": return testCirclePolygon(a, p);\n\t\tcase \"polygon\": return testPolygonPolygon(p, a.pts);\n\t\tcase \"point\": return testPolygonPoint(p, a.pt);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a as Area).shape}`);\n}\n\nexport function testAreaPoint(a: Area, p: Point): boolean {\n\tswitch (a.shape) {\n\t\tcase \"rect\": return testRectPoint(a, p);\n\t\tcase \"line\": return testLinePoint(a, p);\n\t\tcase \"circle\": return testCirclePoint(a, p);\n\t\tcase \"polygon\": return testPolygonPoint(a.pts, p);\n\t\tcase \"point\": return testPointPoint(a.pt, p);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a as Area).shape}`);\n}\n\nexport function testAreaArea(a1: Area, a2: Area): boolean {\n\tswitch (a2.shape) {\n\t\tcase \"rect\": return testAreaRect(a1, a2);\n\t\tcase \"line\": return testAreaLine(a1, a2);\n\t\tcase \"circle\": return testAreaCircle(a1, a2);\n\t\tcase \"polygon\": return testAreaPolygon(a1, a2.pts);\n\t\tcase \"point\": return testAreaPoint(a1, a2.pt);\n\t}\n\tthrow new Error(`Unknown area shape: ${(a2 as Area).shape}`);\n}\n\nexport function minkDiff(r1: Rect, r2: Rect): Rect {\n\treturn {\n\t\tp1: vec2(r1.p1.x - r2.p2.x, r1.p1.y - r2.p2.y),\n\t\tp2: vec2(r1.p2.x - r2.p1.x, r1.p2.y - r2.p1.y),\n\t};\n}\n\nexport class Line {\n\tp1: Vec2;\n\tp2: Vec2;\n\tconstructor(p1: Vec2, p2: Vec2) {\n\t\tthis.p1 = p1;\n\t\tthis.p2 = p2;\n\t}\n}\n\nexport class Rect {\n\tp1: Vec2;\n\tp2: Vec2;\n\tconstructor(p1: Vec2, p2: Vec2) {\n\t\tthis.p1 = p1;\n\t\tthis.p2 = p2;\n\t}\n}\n\nexport class Circle {\n\tcenter: Vec2;\n\tradius: number;\n\tconstructor(center: Vec2, radius: number) {\n\t\tthis.center = center;\n\t\tthis.radius = radius;\n\t}\n}\n", "export default class FPSCounter {\n\n\tprivate buf: number[] = [];\n\tprivate timer: number = 0;\n\tfps: number = 0;\n\n\ttick(dt: number) {\n\n\t\tthis.buf.push(1 / dt);\n\t\tthis.timer += dt;\n\n\t\tif (this.timer >= 1) {\n\t\t\tthis.timer = 0;\n\t\t\tthis.fps = Math.round(this.buf.reduce((a, b) => a + b) / this.buf.length);\n\t\t\tthis.buf = [];\n\t\t}\n\n\t}\n\n}\n", "export default class Timer {\n\n\ttime: number\n\taction: () => void\n\tfinished: boolean = false\n\tpaused: boolean = false\n\n\tconstructor(time: number, action: () => void) {\n\t\tthis.time = time;\n\t\tthis.action = action;\n\t}\n\n\ttick(dt: number): boolean {\n\t\tif (this.finished || this.paused) return false;\n\t\tthis.time -= dt;\n\t\tif (this.time <= 0) {\n\t\t\tthis.action();\n\t\t\tthis.finished = true;\n\t\t\tthis.time = 0;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\treset(time) {\n\t\tthis.time = time;\n\t\tthis.finished = false;\n\t}\n\n}\n", "import {\n\tvec2,\n\tvec3,\n\tVec3,\n\tRect,\n\tLine,\n\tCircle,\n\tColor,\n\tVec2,\n\tMat4,\n\tQuad,\n\tRNG,\n\tquad,\n\trgb,\n\thsl2rgb,\n\trng,\n\trand,\n\trandi,\n\trandSeed,\n\tchance,\n\tchoose,\n\tclamp,\n\tlerp,\n\tmap,\n\tmapc,\n\twave,\n\ttestAreaRect,\n\ttestAreaLine,\n\ttestAreaCircle,\n\ttestAreaPolygon,\n\ttestAreaPoint,\n\ttestAreaArea,\n\ttestLineLineT,\n\ttestRectRect2,\n\ttestLineLine,\n\ttestRectRect,\n\ttestRectLine,\n\ttestRectPoint,\n\ttestPolygonPoint,\n\ttestLinePolygon,\n\ttestPolygonPolygon,\n\ttestCircleCircle,\n\ttestCirclePoint,\n\ttestRectPolygon,\n\tminkDiff,\n\tdeg2rad,\n\trad2deg,\n} from \"./math\";\n\nimport {\n\tIDList,\n\tdownloadURL,\n\tdownloadBlob,\n\tuid,\n\tdeprecate,\n\tdeprecateMsg,\n\tisDataURL,\n\tdeepEq,\n} from \"./utils\";\n\nimport {\n\tGfxShader,\n\tGfxFont,\n\tTexFilter,\n\tRenderProps,\n\tCharTransform,\n\tCharTransformFunc,\n\tTexWrap,\n\tFormattedText,\n\tFormattedChar,\n\tDrawRectOpt,\n\tDrawLineOpt,\n\tDrawLinesOpt,\n\tDrawTriangleOpt,\n\tDrawPolygonOpt,\n\tDrawCircleOpt,\n\tDrawEllipseOpt,\n\tDrawUVQuadOpt,\n\tVertex,\n\tSpriteData,\n\tSoundData,\n\tFontData,\n\tShaderData,\n\tSpriteLoadSrc,\n\tSpriteLoadOpt,\n\tSpriteAtlasData,\n\tFontLoadOpt,\n\tGfxTexData,\n\tKaboomCtx,\n\tKaboomOpt,\n\tAudioPlay,\n\tAudioPlayOpt,\n\tDrawSpriteOpt,\n\tDrawTextOpt,\n\tGameObj,\n\tEventCanceller,\n\tSceneID,\n\tSceneDef,\n\tCompList,\n\tComp,\n\tTag,\n\tKey,\n\tMouseButton,\n\tTouchID,\n\tCollision,\n\tPosComp,\n\tScaleComp,\n\tRotateComp,\n\tColorComp,\n\tOpacityComp,\n\tOrigin,\n\tOriginComp,\n\tLayerComp,\n\tZComp,\n\tFollowComp,\n\tMoveComp,\n\tOutviewCompOpt,\n\tOutviewComp,\n\tCleanupCompOpt,\n\tCleanupComp,\n\tAreaCompOpt,\n\tAreaComp,\n\tArea,\n\tSpriteComp,\n\tSpriteCompOpt,\n\tGfxTexture,\n\tSpriteAnimPlayOpt,\n\tTextComp,\n\tTextCompOpt,\n\tRectComp,\n\tRectCompOpt,\n\tUVQuadComp,\n\tCircleComp,\n\tOutlineComp,\n\tTimerComp,\n\tBodyComp,\n\tBodyCompOpt,\n\tUniform,\n\tShaderComp,\n\tSolidComp,\n\tFixedComp,\n\tStayComp,\n\tHealthComp,\n\tLifespanComp,\n\tLifespanCompOpt,\n\tStateComp,\n\tDebug,\n\tKaboomPlugin,\n\tMergeObj,\n\tLevel,\n\tLevelOpt,\n\tCursor,\n\tRecording,\n\tKaboom,\n} from \"./types\";\n\nimport FPSCounter from \"./fps\";\nimport Timer from \"./timer\";\n\n// @ts-ignore\nimport apl386Src from \"./assets/apl386.png\";\n// @ts-ignore\nimport apl386oSrc from \"./assets/apl386o.png\";\n// @ts-ignore\nimport sinkSrc from \"./assets/sink.png\";\n// @ts-ignore\nimport sinkoSrc from \"./assets/sinko.png\";\n// @ts-ignore\nimport beanSrc from \"./assets/bean.png\";\n// @ts-ignore\nimport burpBytes from \"./assets/burp.mp3\";\n// @ts-ignore\nimport kaSrc from \"./assets/ka.png\";\n// @ts-ignore\nimport boomSrc from \"./assets/boom.png\";\n\ntype ButtonState =\n\t\"up\"\n\t| \"pressed\"\n\t| \"rpressed\"\n\t| \"down\"\n\t| \"released\"\n\t;\n\ntype DrawTextureOpt = RenderProps & {\n\ttex: GfxTexture,\n\twidth?: number,\n\theight?: number,\n\ttiled?: boolean,\n\tflipX?: boolean,\n\tflipY?: boolean,\n\tquad?: Quad,\n\torigin?: Origin | Vec2,\n}\n\ninterface GfxTexOpt {\n\tfilter?: TexFilter,\n\twrap?: TexWrap,\n}\n\n// translate these key names to a simpler version\nconst KEY_ALIAS = {\n\t\"ArrowLeft\": \"left\",\n\t\"ArrowRight\": \"right\",\n\t\"ArrowUp\": \"up\",\n\t\"ArrowDown\": \"down\",\n\t\" \": \"space\",\n};\n\n// according to https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\nconst MOUSE_BUTTONS = [\n\t\"left\",\n\t\"middle\",\n\t\"right\",\n\t\"back\",\n\t\"forward\",\n];\n\n// don't trigger browser default event when these keys are pressed\nconst PREVENT_DEFAULT_KEYS = [\n\t\"space\",\n\t\"left\",\n\t\"right\",\n\t\"up\",\n\t\"down\",\n\t\"tab\",\n\t\"f1\",\n\t\"f2\",\n\t\"f3\",\n\t\"f4\",\n\t\"f5\",\n\t\"f6\",\n\t\"f7\",\n\t\"f8\",\n\t\"f9\",\n\t\"f10\",\n\t\"f11\",\n\t\"s\",\n];\n\n// some default charsets for loading bitmap fonts\nconst ASCII_CHARS = \" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\";\nconst CP437_CHARS = \" \u263A\u263B\u2665\u2666\u2663\u2660\u2022\u25D8\u25CB\u25D9\u2642\u2640\u266A\u266B\u263C\u25BA\u25C4\u2195\u203C\u00B6\u00A7\u25AC\u21A8\u2191\u2193\u2192\u2190\u221F\u2194\u25B2\u25BC !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9\u00FF\u00D6\u00DC\u00A2\u00A3\u00A5\u20A7\u0192\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA\u00BF\u2310\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\u00DF\u0393\u03C0\u03A3\u03C3\u00B5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\u00B1\u2265\u2264\u2320\u2321\u00F7\u2248\u00B0\u2219\u00B7\u221A\u207F\u00B2\u25A0\";\n\n// audio gain range\nconst MIN_GAIN = 0;\nconst MAX_GAIN = 3;\n\n// audio speed range\nconst MIN_SPEED = 0;\nconst MAX_SPEED = 3;\n\n// audio detune range\nconst MIN_DETUNE = -1200;\nconst MAX_DETUNE = 1200;\n\nconst DEF_ORIGIN = \"topleft\";\nconst DEF_GRAVITY = 1600;\nconst QUEUE_COUNT = 65536;\nconst BG_GRID_SIZE = 64;\n\nconst DEF_FONT = \"apl386o\";\nconst DBG_FONT = \"sink\";\n\n// vertex format stride (vec3 pos, vec2 uv, vec4 color)\nconst STRIDE = 9;\n\n// vertex shader template, replace {{user}} with user vertex shader code\nconst VERT_TEMPLATE = `\nattribute vec3 a_pos;\nattribute vec2 a_uv;\nattribute vec4 a_color;\n\nvarying vec3 v_pos;\nvarying vec2 v_uv;\nvarying vec4 v_color;\n\nvec4 def_vert() {\n\treturn vec4(a_pos, 1.0);\n}\n\n{{user}}\n\nvoid main() {\n\tvec4 pos = vert(a_pos, a_uv, a_color);\n\tv_pos = a_pos;\n\tv_uv = a_uv;\n\tv_color = a_color;\n\tgl_Position = pos;\n}\n`;\n\n// fragment shader template, replace {{user}} with user fragment shader code\nconst FRAG_TEMPLATE = `\nprecision mediump float;\n\nvarying vec3 v_pos;\nvarying vec2 v_uv;\nvarying vec4 v_color;\n\nuniform sampler2D u_tex;\n\nvec4 def_frag() {\n\treturn v_color * texture2D(u_tex, v_uv);\n}\n\n{{user}}\n\nvoid main() {\n\tgl_FragColor = frag(v_pos, v_uv, v_color, u_tex);\n\tif (gl_FragColor.a == 0.0) {\n\t\tdiscard;\n\t}\n}\n`;\n\n// default {{user}} vertex shader code\nconst DEF_VERT = `\nvec4 vert(vec3 pos, vec2 uv, vec4 color) {\n\treturn def_vert();\n}\n`;\n\n// default {{user}} fragment shader code\nconst DEF_FRAG = `\nvec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) {\n\treturn def_frag();\n}\n`;\n\nconst COMP_DESC = new Set([\n\t\"id\",\n\t\"require\",\n]);\n\nconst COMP_EVENTS = new Set([\n\t\"add\",\n\t\"load\",\n\t\"update\",\n\t\"draw\",\n\t\"destroy\",\n\t\"inspect\",\n]);\n\n// transform the button state to the next state\n// e.g. a button might become \"pressed\" one frame, and it should become \"down\" next frame\nfunction processButtonState(s: ButtonState): ButtonState {\n\tif (s === \"pressed\" || s === \"rpressed\") {\n\t\treturn \"down\";\n\t}\n\tif (s === \"released\") {\n\t\treturn \"up\";\n\t}\n\treturn s;\n}\n\n// wrappers around full screen functions to work across browsers\nfunction enterFullscreen(el: HTMLElement) {\n\tif (el.requestFullscreen) el.requestFullscreen();\n\t// @ts-ignore\n\telse if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();\n};\n\nfunction exitFullscreen() {\n\tif (document.exitFullscreen) document.exitFullscreen();\n\t// @ts-ignore\n\telse if (document.webkitExitFullScreen) document.webkitExitFullScreen();\n};\n\nfunction getFullscreenElement(): Element | void {\n\treturn document.fullscreenElement\n\t\t// @ts-ignore\n\t\t|| document.webkitFullscreenElement\n\t\t;\n};\n\n// convert origin string to a vec2 offset\nfunction originPt(orig: Origin | Vec2): Vec2 {\n\tswitch (orig) {\n\t\tcase \"topleft\": return vec2(-1, -1);\n\t\tcase \"top\": return vec2(0, -1);\n\t\tcase \"topright\": return vec2(1, -1);\n\t\tcase \"left\": return vec2(-1, 0);\n\t\tcase \"center\": return vec2(0, 0);\n\t\tcase \"right\": return vec2(1, 0);\n\t\tcase \"botleft\": return vec2(-1, 1);\n\t\tcase \"bot\": return vec2(0, 1);\n\t\tcase \"botright\": return vec2(1, 1);\n\t\tdefault: return orig;\n\t}\n}\n\nfunction createEmptyAudioBuffer() {\n\treturn new AudioBuffer({\n\t\tlength: 1,\n\t\tnumberOfChannels: 1,\n\t\tsampleRate: 44100\n\t});\n}\n\n// only exports one kaboom() which contains all the state\nexport default (gopt: KaboomOpt = {}): KaboomCtx => {\n\nconst app = (() => {\n\n\tconst root = gopt.root ?? document.body;\n\n\t// if root is not defined (which falls back to ) we assume user is using kaboom on a clean page, and modify to better fit a full screen canvas\n\tif (root === document.body) {\n\t\tdocument.body.style[\"width\"] = \"100%\";\n\t\tdocument.body.style[\"height\"] = \"100%\";\n\t\tdocument.body.style[\"margin\"] = \"0px\";\n\t\tdocument.documentElement.style[\"width\"] = \"100%\";\n\t\tdocument.documentElement.style[\"height\"] = \"100%\";\n\t}\n\n\t// create a if user didn't provide one\n\tconst canvas = gopt.canvas ?? (() => {\n\t\tconst canvas = document.createElement(\"canvas\");\n\t\troot.appendChild(canvas);\n\t\treturn canvas;\n\t})();\n\n\t// global pixel scale\n\tconst gscale = gopt.scale ?? 1;\n\n\t// adjust canvas size according to user size / viewport settings\n\tif (gopt.width && gopt.height && !gopt.stretch && !gopt.letterbox) {\n\t\tcanvas.width = gopt.width * gscale;\n\t\tcanvas.height = gopt.height * gscale;\n\t} else {\n\t\tcanvas.width = canvas.parentElement.offsetWidth;\n\t\tcanvas.height = canvas.parentElement.offsetHeight;\n\t}\n\n\t// canvas css styles\n\tconst styles = [\n\t\t\"outline: none\",\n\t\t\"cursor: default\",\n\t];\n\n\tif (gopt.crisp) {\n\t\tstyles.push(\"image-rendering: pixelated\");\n\t\tstyles.push(\"image-rendering: crisp-edges\");\n\t}\n\n\t// TODO: .style is supposed to be readonly? alternative?\n\t// @ts-ignore\n\tcanvas.style = styles.join(\";\");\n\n\t// make canvas focusable\n\tcanvas.setAttribute(\"tabindex\", \"0\");\n\n\t// create webgl context\n\tconst gl = canvas\n\t\t.getContext(\"webgl\", {\n\t\t\tantialias: true,\n\t\t\tdepth: true,\n\t\t\tstencil: true,\n\t\t\talpha: true,\n\t\t\tpreserveDrawingBuffer: true,\n\t\t});\n\n\treturn {\n\n\t\tcanvas: canvas,\n\t\tscale: gscale,\n\t\tgl: gl,\n\n\t\t// keep track of all button states\n\t\tkeyStates: {} as Record,\n\t\tmouseStates: {} as Record,\n\n\t\t// input states from last frame, should reset every frame\n\t\tcharInputted: [],\n\t\tisMouseMoved: false,\n\t\tisKeyPressed: false,\n\t\tisKeyPressedRepeat: false,\n\t\tisKeyReleased: false,\n\t\tmousePos: vec2(0, 0),\n\t\tmouseDeltaPos: vec2(0, 0),\n\n\t\t// total time elapsed\n\t\ttime: 0,\n\t\t// real total time elapsed (including paused time)\n\t\trealTime: 0,\n\t\t// if we should skip next dt, to prevent the massive dt surge if user switch to another tab for a while and comeback\n\t\tskipTime: false,\n\t\t// how much time last frame took\n\t\tdt: 0.0,\n\t\t// total frames elapsed\n\t\tnumFrames: 0,\n\n\t\t// if we're on a touch device\n\t\tisTouch: (\"ontouchstart\" in window) || navigator.maxTouchPoints > 0,\n\n\t\t// requestAnimationFrame id\n\t\tloopID: null,\n\t\t// if our game loop is currently stopped / paused\n\t\tstopped: false,\n\t\tpaused: false,\n\n\t\t// TODO: take fps counter out pure\n\t\tfpsCounter: new FPSCounter(),\n\n\t\t// if we finished loading all assets\n\t\tloaded: false,\n\n\t};\n\n})();\n\nconst gfx = (() => {\n\n\tconst gl = app.gl;\n\tconst defShader = makeShader(DEF_VERT, DEF_FRAG);\n\n\t// a 1x1 white texture to draw raw shapes like rectangles and polygons\n\t// we use a texture for those so we can use only 1 pipeline for drawing sprites + shapes\n\tconst emptyTex = makeTex(\n\t\tnew ImageData(new Uint8ClampedArray([ 255, 255, 255, 255, ]), 1, 1)\n\t);\n\n\tconst c = gopt.background ?? rgb(0, 0, 0);\n\n\tif (gopt.background) {\n\t\tconst c = Color.fromArray(gopt.background);\n\t\tgl.clearColor(c.r / 255, c.g / 255, c.b / 255, 1);\n\t}\n\n\tgl.enable(gl.BLEND);\n\tgl.enable(gl.SCISSOR_TEST);\n\tgl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n\t// we only use one vertex and index buffer that batches all draw calls\n\tconst vbuf = gl.createBuffer();\n\n\tgl.bindBuffer(gl.ARRAY_BUFFER, vbuf);\n\t// vec3 pos\n\tgl.vertexAttribPointer(0, 3, gl.FLOAT, false, STRIDE * 4, 0);\n\tgl.enableVertexAttribArray(0);\n\t// vec2 uv\n\tgl.vertexAttribPointer(1, 2, gl.FLOAT, false, STRIDE * 4, 12);\n\tgl.enableVertexAttribArray(1);\n\t// vec4 color\n\tgl.vertexAttribPointer(2, 4, gl.FLOAT, false, STRIDE * 4, 20);\n\tgl.enableVertexAttribArray(2);\n\tgl.bufferData(gl.ARRAY_BUFFER, QUEUE_COUNT * 4, gl.DYNAMIC_DRAW);\n\tgl.bindBuffer(gl.ARRAY_BUFFER, null);\n\n\tconst ibuf = gl.createBuffer();\n\n\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibuf);\n\tgl.bufferData(gl.ELEMENT_ARRAY_BUFFER, QUEUE_COUNT * 2, gl.DYNAMIC_DRAW);\n\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);\n\n\t// a checkerboard texture used for the default background\n\tconst bgTex = makeTex(\n\t\tnew ImageData(new Uint8ClampedArray([\n\t\t\t128, 128, 128, 255,\n\t\t\t190, 190, 190, 255,\n\t\t\t190, 190, 190, 255,\n\t\t\t128, 128, 128, 255,\n\t\t]), 2, 2), {\n\t\t\twrap: \"repeat\",\n\t\t\tfilter: \"nearest\",\n\t\t},\n\t);\n\n\treturn {\n\n\t\t// keep track of how many draw calls we're doing this frame\n\t\tdrawCalls: 0,\n\t\t// how many draw calls we're doing last frame, this is the number we give to users\n\t\tlastDrawCalls: 0,\n\n\t\t// gfx states\n\t\tdefShader: defShader,\n\t\tcurShader: defShader,\n\t\tdefTex: emptyTex,\n\t\tcurTex: emptyTex,\n\t\tcurUniform: {},\n\t\tvbuf: vbuf,\n\t\tibuf: ibuf,\n\n\t\t// local vertex / index buffer queue\n\t\tvqueue: [],\n\t\tiqueue: [],\n\n\t\ttransform: new Mat4(),\n\t\ttransformStack: [],\n\n\t\tbgTex: bgTex,\n\n\t\twidth: gopt.width,\n\t\theight: gopt.height,\n\n\t\tviewport: {\n\t\t\tx: 0,\n\t\t\ty: 0,\n\t\t\twidth: gl.drawingBufferWidth,\n\t\t\theight: gl.drawingBufferHeight,\n\t\t},\n\n\t};\n\n})();\n\nupdateViewport();\n\nconst audio = (() => {\n\n\t// TODO: handle when audio context is unavailable\n\tconst ctx = new (window.AudioContext || (window as any).webkitAudioContext)() as AudioContext\n\tconst masterNode = ctx.createGain();\n\tmasterNode.connect(ctx.destination);\n\n\t// by default browsers can only load audio async, we don't deal with that and just start with an empty audio buffer\n\tconst burpSnd = {\n\t\tbuf: createEmptyAudioBuffer(),\n\t};\n\n\t// load that burp sound\n\tctx.decodeAudioData(burpBytes.buffer.slice(0), (buf) => {\n\t\tburpSnd.buf = buf;\n\t}, () => {\n\t\tthrow new Error(\"Failed to load burp.\")\n\t});\n\n\treturn {\n\t\tctx,\n\t\tmasterNode,\n\t\tburpSnd,\n\t};\n\n})();\n\nconst assets = {\n\n\t// keep track of how many assets are loading / loaded, for calculaating progress\n\tnumLoading: 0,\n\tnumLoaded: 0,\n\n\t// prefix for when loading from a url\n\turlPrefix: \"\",\n\n\t// asset holders\n\tsprites: {},\n\tsounds: {},\n\tshaders: {},\n\tfonts: {},\n\n};\n\nconst game = {\n\n\t// event callbacks\n\tevents: {},\n\tobjEvents: {},\n\n\t// root game object\n\t// these transforms are used as camera\n\troot: make([]),\n\n\ttimers: new IDList(),\n\n\t// misc\n\tlayers: {},\n\tdefLayer: null,\n\tgravity: DEF_GRAVITY,\n\ton(ev: string, cb: F): EventCanceller {\n\t\tif (!this.events[ev]) {\n\t\t\tthis.events[ev] = new IDList();\n\t\t}\n\t\treturn this.events[ev].pushd(cb);\n\t},\n\ttrigger(ev: string, ...args) {\n\t\tif (this.events[ev]) {\n\t\t\tthis.events[ev].forEach((cb) => cb(...args));\n\t\t}\n\t},\n\tscenes: {},\n\n\t// on screen log\n\tlogs: [],\n\n\t// camera\n\tcam: {\n\t\tpos: center(),\n\t\tscale: vec2(1),\n\t\tangle: 0,\n\t\tshake: 0,\n\t\ttransform: new Mat4(),\n\t},\n\n}\n\n// wrap individual loaders with global loader counter, for stuff like progress bar\nfunction load(prom: Promise): Promise {\n\n\tassets.numLoading++;\n\n\t// wrapping another layer of promise because we are catching errors here internally and we also want users be able to catch errors, however only one catch is allowed per promise chain\n\treturn new Promise((resolve, reject) => {\n\t\tprom\n\t\t\t.then(resolve)\n\t\t\t.catch((err) => {\n\t\t\t\tdebug.error(err);\n\t\t\t\treject(err);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tassets.numLoading--;\n\t\t\t\tassets.numLoaded++;\n\t\t\t});\n\t}) as Promise;\n\n}\n\n// get current load progress\nfunction loadProgress(): number {\n\treturn assets.numLoaded / (assets.numLoading + assets.numLoaded);\n}\n\n// global load path prefix\nfunction loadRoot(path?: string): string {\n\tif (path !== undefined) {\n\t\tassets.urlPrefix = path;\n\t}\n\treturn assets.urlPrefix;\n}\n\n// wrapper around fetch() that applies urlPrefix and basic error handling\nfunction fetchURL(path: string) {\n\tconst url = assets.urlPrefix + path;\n\treturn fetch(url)\n\t\t.then((res) => {\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new Error(`Failed to fetch ${url}`);\n\t\t\t}\n\t\t\treturn res;\n\t\t});\n}\n\n// wrapper around image loader to get a Promise\nfunction loadImg(src: string): Promise {\n\tconst img = new Image();\n\timg.src = isDataURL(src) ? src : assets.urlPrefix + src;\n\timg.crossOrigin = \"anonymous\";\n\treturn new Promise((resolve, reject) => {\n\t\timg.onload = () => resolve(img);\n\t\t// TODO: truncate for long dataurl src\n\t\timg.onerror = () => reject(`Failed to load image from \"${src}\"`);\n\t});\n}\n\n// TODO: support SpriteLoadSrc\nfunction loadFont(\n\tname: string | null,\n\tsrc: string,\n\tgw: number,\n\tgh: number,\n\topt: FontLoadOpt = {},\n): Promise {\n\treturn load(loadImg(src)\n\t\t.then((img) => {\n\t\t\tconst font = makeFont(\n\t\t\t\tmakeTex(img, opt),\n\t\t\t\tgw,\n\t\t\t\tgh,\n\t\t\t\topt.chars ?? ASCII_CHARS\n\t\t\t);\n\t\t\tif (name) {\n\t\t\t\tassets.fonts[name] = font;\n\t\t\t}\n\t\t\treturn font;\n\t\t})\n\t);\n}\n\nfunction getSprite(name: string): SpriteData | null {\n\treturn assets.sprites[name] ?? null;\n}\n\nfunction getSound(name: string): SoundData | null {\n\treturn assets.sounds[name] ?? null;\n}\n\nfunction getFont(name: string): FontData | null {\n\treturn assets.fonts[name] ?? null;\n}\n\nfunction getShader(name: string): ShaderData | null {\n\treturn assets.shaders[name] ?? null;\n}\n\n// get an array of frames based on configuration on how to slice the image\nfunction slice(x = 1, y = 1, dx = 0, dy = 0, w = 1, h = 1): Quad[] {\n\tconst frames = [];\n\tconst qw = w / x;\n\tconst qh = h / y;\n\tfor (let j = 0; j < y; j++) {\n\t\tfor (let i = 0; i < x; i++) {\n\t\t\tframes.push(new Quad(\n\t\t\t\tdx + i * qw,\n\t\t\t\tdy + j * qh,\n\t\t\t\tqw,\n\t\t\t\tqh,\n\t\t\t));\n\t\t}\n\t}\n\treturn frames;\n}\n\nfunction loadSpriteAtlas(\n\tsrc: SpriteLoadSrc,\n\tdata: SpriteAtlasData | string\n): Promise> {\n\tif (typeof data === \"string\") {\n\t\t// TODO: this adds a new loader asyncly\n\t\treturn load(fetchURL(data)\n\t\t\t.then((res) => res.json())\n\t\t\t.then((data2) => loadSpriteAtlas(src, data2)));\n\t}\n\treturn load(loadSprite(null, src).then((atlas) => {\n\t\tconst map = {};\n\t\tconst w = atlas.tex.width;\n\t\tconst h = atlas.tex.height;\n\t\tfor (const name in data) {\n\t\t\tconst info = data[name];\n\t\t\tconst spr = {\n\t\t\t\ttex: atlas.tex,\n\t\t\t\tframes: slice(info.sliceX, info.sliceY, info.x / w, info.y / h, info.width / w, info.height / h),\n\t\t\t\tanims: info.anims,\n\t\t\t}\n\t\t\tassets.sprites[name] = spr;\n\t\t\tmap[name] = spr;\n\t\t}\n\t\treturn map;\n\t}));\n}\n\n// synchronously load sprite from local pixel data\nfunction loadRawSprite(\n\tname: string | null,\n\tsrc: GfxTexData,\n\topt: SpriteLoadOpt = {}\n) {\n\n\tconst tex = makeTex(src, opt);\n\tconst frames = slice(opt.sliceX || 1, opt.sliceY || 1);\n\n\tconst sprite = {\n\t\ttex: tex,\n\t\tframes: frames,\n\t\tanims: opt.anims || {},\n\t};\n\n\tif (name) {\n\t\tassets.sprites[name] = sprite;\n\t}\n\n\treturn sprite;\n\n}\n\n// load a sprite to asset manager\nfunction loadSprite(\n\tname: string | null,\n\tsrc: SpriteLoadSrc,\n\topt: SpriteLoadOpt = {\n\t\tsliceX: 1,\n\t\tsliceY: 1,\n\t\tanims: {},\n\t},\n): Promise {\n\n\treturn load(new Promise((resolve, reject) => {\n\n\t\tif (!src) {\n\t\t\treturn reject(`Expected sprite src for \"${name}\"`);\n\t\t}\n\n\t\t// from url\n\t\tif (typeof(src) === \"string\") {\n\t\t\tloadImg(src)\n\t\t\t\t.then((img) => resolve(loadRawSprite(name, img, opt)))\n\t\t\t\t.catch(reject);\n\t\t} else {\n\t\t\tresolve(loadRawSprite(name, src, opt));\n\t\t}\n\n\t}));\n\n}\n\n// TODO: accept raw json\nfunction loadPedit(name: string, src: string): Promise {\n\n\treturn load(new Promise((resolve, reject) => {\n\n\t\tfetchURL(src)\n\t\t\t.then((res) => res.json())\n\t\t\t.then(async (data) => {\n\n\t\t\t\tconst images = await Promise.all(data.frames.map(loadImg));\n\t\t\t\tconst canvas = document.createElement(\"canvas\");\n\t\t\t\tcanvas.width = data.width;\n\t\t\t\tcanvas.height = data.height * data.frames.length;\n\n\t\t\t\tconst ctx = canvas.getContext(\"2d\");\n\n\t\t\t\timages.forEach((img: HTMLImageElement, i) => {\n\t\t\t\t\tctx.drawImage(img, 0, i * data.height);\n\t\t\t\t});\n\n\t\t\t\treturn loadSprite(name, canvas, {\n\t\t\t\t\tsliceY: data.frames.length,\n\t\t\t\t\tanims: data.anims,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.then(resolve)\n\t\t\t.catch(reject)\n\t\t\t;\n\n\t}));\n\n}\n\n// TODO: accept raw json\nfunction loadAseprite(\n\tname: string | null,\n\timgSrc: SpriteLoadSrc,\n\tjsonSrc: string\n): Promise {\n\n\treturn load(new Promise((resolve, reject) => {\n\n\t\tloadSprite(name, imgSrc)\n\t\t\t.then((sprite: SpriteData) => {\n\t\t\t\tfetchURL(jsonSrc)\n\t\t\t\t\t.then((res) => res.json())\n\t\t\t\t\t.then((data) => {\n\t\t\t\t\t\tconst size = data.meta.size;\n\t\t\t\t\t\tsprite.frames = data.frames.map((f: any) => {\n\t\t\t\t\t\t\treturn new Quad(\n\t\t\t\t\t\t\t\tf.frame.x / size.w,\n\t\t\t\t\t\t\t\tf.frame.y / size.h,\n\t\t\t\t\t\t\t\tf.frame.w / size.w,\n\t\t\t\t\t\t\t\tf.frame.h / size.h,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor (const anim of data.meta.frameTags) {\n\t\t\t\t\t\t\tif (anim.from === anim.to) {\n\t\t\t\t\t\t\t\tsprite.anims[anim.name] = anim.from\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsprite.anims[anim.name] = {\n\t\t\t\t\t\t\t\t\tfrom: anim.from,\n\t\t\t\t\t\t\t\t\tto: anim.to,\n\t\t\t\t\t\t\t\t\t// TODO: let users define these\n\t\t\t\t\t\t\t\t\tspeed: 10,\n\t\t\t\t\t\t\t\t\tloop: true,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve(sprite);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(reject)\n\t\t\t\t\t;\n\t\t\t})\n\t\t\t.catch(reject);\n\n\t}));\n\n}\n\nfunction loadShader(\n\tname: string | null,\n\tvert?: string,\n\tfrag?: string,\n\tisUrl: boolean = false,\n): Promise {\n\n\tfunction loadRawShader(\n\t\tname: string | null,\n\t\tvert: string | null,\n\t\tfrag: string | null,\n\t): ShaderData {\n\t\tconst shader = makeShader(vert, frag);\n\t\tif (name) {\n\t\t\tassets.shaders[name] = shader;\n\t\t}\n\t\treturn shader;\n\t}\n\n\treturn load(new Promise((resolve, reject) => {\n\n\t\tif (!vert && !frag) {\n\t\t\treturn reject(\"no shader\");\n\t\t}\n\n\t\tfunction resolveUrl(url?: string) {\n\t\t\treturn url ?\n\t\t\t\tfetchURL(url)\n\t\t\t\t\t.then((res) => res.text())\n\t\t\t\t\t.catch(reject)\n\t\t\t\t: new Promise((r) => r(null));\n\t\t}\n\n\t\tif (isUrl) {\n\t\t\tPromise.all([resolveUrl(vert), resolveUrl(frag)])\n\t\t\t\t.then(([vcode, fcode]: [string | null, string | null]) => {\n\t\t\t\t\tresolve(loadRawShader(name, vcode, fcode));\n\t\t\t\t})\n\t\t\t\t.catch(reject);\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tresolve(loadRawShader(name, vert, frag));\n\t\t\t} catch (err) {\n\t\t\t\treject(err);\n\t\t\t}\n\t\t}\n\n\t}));\n\n}\n\n// TODO: accept dataurl\n// load a sound to asset manager\nfunction loadSound(\n\tname: string | null,\n\tsrc: string,\n): Promise {\n\n\treturn load(new Promise((resolve, reject) => {\n\n\t\tif (!src) {\n\t\t\treturn reject(`expected sound src for \"${name}\"`);\n\t\t}\n\n\t\t// from url\n\t\tif (typeof(src) === \"string\") {\n\t\t\tfetchURL(src)\n\t\t\t\t.then((res) => res.arrayBuffer())\n\t\t\t\t.then((data) => {\n\t\t\t\t\treturn new Promise((resolve2, reject2) =>\n\t\t\t\t\t\taudio.ctx.decodeAudioData(data, resolve2, reject2)\n\t\t\t\t\t);\n\t\t\t\t})\n\t\t\t\t.then((buf: AudioBuffer) => {\n\t\t\t\t\tconst snd = {\n\t\t\t\t\t\tbuf: buf,\n\t\t\t\t\t}\n\t\t\t\t\tif (name) {\n\t\t\t\t\t\tassets.sounds[name] = snd;\n\t\t\t\t\t}\n\t\t\t\t\tresolve(snd);\n\t\t\t\t})\n\t\t\t\t.catch(reject);\n\t\t}\n\n\t}));\n\n}\n\nfunction loadBean(name: string = \"bean\"): Promise {\n\treturn loadSprite(name, beanSrc);\n}\n\n// get / set master volume\nfunction volume(v?: number): number {\n\tif (v !== undefined) {\n\t\taudio.masterNode.gain.value = clamp(v, MIN_GAIN, MAX_GAIN);\n\t}\n\treturn audio.masterNode.gain.value;\n}\n\n// plays a sound, returns a control handle\nfunction play(\n\tsrc: SoundData | string,\n\topt: AudioPlayOpt = {\n\t\tloop: false,\n\t\tvolume: 1,\n\t\tspeed: 1,\n\t\tdetune: 0,\n\t\tseek: 0,\n\t},\n): AudioPlay {\n\n\t// TODO: clean?\n\tif (typeof src === \"string\") {\n\n\t\tconst pb = play({\n\t\t\tbuf: createEmptyAudioBuffer(),\n\t\t});\n\n\t\tonLoad(() => {\n\t\t\tconst snd = assets.sounds[src];\n\t\t\tif (!snd) {\n\t\t\t\tthrow new Error(`Sound not found: \"${src}\"`);\n\t\t\t}\n\t\t\tconst pb2 = play(snd, opt);\n\t\t\tfor (const k in pb2) {\n\t\t\t\tpb[k] = pb2[k];\n\t\t\t}\n\t\t});\n\n\t\treturn pb;\n\n\t}\n\n\tconst ctx = audio.ctx;\n\tlet stopped = false;\n\tlet srcNode = ctx.createBufferSource();\n\n\tsrcNode.buffer = src.buf;\n\tsrcNode.loop = opt.loop ? true : false;\n\n\tconst gainNode = ctx.createGain();\n\n\tsrcNode.connect(gainNode);\n\tgainNode.connect(audio.masterNode);\n\n\tconst pos = opt.seek ?? 0;\n\n\tsrcNode.start(0, pos);\n\n\tlet startTime = ctx.currentTime - pos;\n\tlet stopTime: number | null = null;\n\n\tconst handle = {\n\n\t\tstop() {\n\t\t\tif (stopped) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.pause();\n\t\t\tstartTime = ctx.currentTime;\n\t\t},\n\n\t\tplay(seek?: number) {\n\n\t\t\tif (!stopped) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst oldNode = srcNode;\n\n\t\t\tsrcNode = ctx.createBufferSource();\n\t\t\tsrcNode.buffer = oldNode.buffer;\n\t\t\tsrcNode.loop = oldNode.loop;\n\t\t\tsrcNode.playbackRate.value = oldNode.playbackRate.value;\n\n\t\t\tif (srcNode.detune) {\n\t\t\t\tsrcNode.detune.value = oldNode.detune.value;\n\t\t\t}\n\n\t\t\tsrcNode.connect(gainNode);\n\n\t\t\tconst pos = seek ?? this.time();\n\n\t\t\tsrcNode.start(0, pos);\n\t\t\tstartTime = ctx.currentTime - pos;\n\t\t\tstopped = false;\n\t\t\tstopTime = null;\n\n\t\t},\n\n\t\tpause() {\n\t\t\tif (stopped) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsrcNode.stop();\n\t\t\tstopped = true;\n\t\t\tstopTime = ctx.currentTime;\n\t\t},\n\n\t\tisPaused(): boolean {\n\t\t\treturn stopped;\n\t\t},\n\n\t\tpaused(): boolean {\n\t\t\tdeprecateMsg(\"paused()\", \"isPaused()\");\n\t\t\treturn this.isPaused();\n\t\t},\n\n\t\tisStopped(): boolean {\n\t\t\treturn stopped;\n\t\t},\n\n\t\tstopped(): boolean {\n\t\t\tdeprecateMsg(\"stopped()\", \"isStopped()\");\n\t\t\treturn this.isStopped();\n\t\t},\n\n\t\t// TODO: affect time()\n\t\tspeed(val?: number): number {\n\t\t\tif (val !== undefined) {\n\t\t\t\tsrcNode.playbackRate.value = clamp(val, MIN_SPEED, MAX_SPEED);\n\t\t\t}\n\t\t\treturn srcNode.playbackRate.value;\n\t\t},\n\n\t\tdetune(val?: number): number {\n\t\t\tif (!srcNode.detune) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (val !== undefined) {\n\t\t\t\tsrcNode.detune.value = clamp(val, MIN_DETUNE, MAX_DETUNE);\n\t\t\t}\n\t\t\treturn srcNode.detune.value;\n\t\t},\n\n\t\tvolume(val?: number): number {\n\t\t\tif (val !== undefined) {\n\t\t\t\tgainNode.gain.value = clamp(val, MIN_GAIN, MAX_GAIN);\n\t\t\t}\n\t\t\treturn gainNode.gain.value;\n\t\t},\n\n\t\tloop() {\n\t\t\tsrcNode.loop = true;\n\t\t},\n\n\t\tunloop() {\n\t\t\tsrcNode.loop = false;\n\t\t},\n\n\t\tduration(): number {\n\t\t\treturn src.buf.duration;\n\t\t},\n\n\t\ttime(): number {\n\t\t\tif (stopped) {\n\t\t\t\treturn stopTime - startTime;\n\t\t\t} else {\n\t\t\t\treturn ctx.currentTime - startTime;\n\t\t\t}\n\t\t},\n\n\t};\n\n\thandle.speed(opt.speed);\n\thandle.detune(opt.detune);\n\thandle.volume(opt.volume);\n\n\treturn handle;\n\n}\n\n// core kaboom logic\nfunction burp(opt?: AudioPlayOpt): AudioPlay {\n\treturn play(audio.burpSnd, opt);\n}\n\n// TODO: take these webgl structures out pure\nfunction makeTex(\n\tdata: GfxTexData,\n\topt: GfxTexOpt = {}\n): GfxTexture {\n\n\tconst gl = app.gl;\n\tconst id = gl.createTexture();\n\n\tgl.bindTexture(gl.TEXTURE_2D, id);\n\tgl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);\n\n\tconst filter = (() => {\n\t\tswitch (opt.filter ?? gopt.texFilter) {\n\t\t\tcase \"linear\": return gl.LINEAR;\n\t\t\tcase \"nearest\": return gl.NEAREST;\n\t\t\tdefault: return gl.NEAREST;\n\t\t}\n\t})();\n\n\tconst wrap = (() => {\n\t\tswitch (opt.wrap) {\n\t\t\tcase \"repeat\": return gl.REPEAT;\n\t\t\tcase \"clampToEdge\": return gl.CLAMP_TO_EDGE;\n\t\t\tdefault: return gl.CLAMP_TO_EDGE;\n\t\t}\n\t})();\n\n\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);\n\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n\tgl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n\tgl.bindTexture(gl.TEXTURE_2D, null);\n\n\treturn {\n\t\twidth: data.width,\n\t\theight: data.height,\n\t\tbind() {\n\t\t\tgl.bindTexture(gl.TEXTURE_2D, id);\n\t\t},\n\t\tunbind() {\n\t\t\tgl.bindTexture(gl.TEXTURE_2D, null);\n\t\t},\n\t};\n\n}\n\nfunction makeShader(\n\tvertSrc: string | null = DEF_VERT,\n\tfragSrc: string | null = DEF_FRAG,\n): GfxShader {\n\n\tconst gl = app.gl;\n\tlet msg;\n\tconst vcode = VERT_TEMPLATE.replace(\"{{user}}\", vertSrc ?? DEF_VERT);\n\tconst fcode = FRAG_TEMPLATE.replace(\"{{user}}\", fragSrc ?? DEF_FRAG);\n\tconst vertShader = gl.createShader(gl.VERTEX_SHADER);\n\tconst fragShader = gl.createShader(gl.FRAGMENT_SHADER);\n\n\tgl.shaderSource(vertShader, vcode);\n\tgl.shaderSource(fragShader, fcode);\n\tgl.compileShader(vertShader);\n\tgl.compileShader(fragShader);\n\n\tif ((msg = gl.getShaderInfoLog(vertShader))) {\n\t\tthrow new Error(msg);\n\t}\n\n\tif ((msg = gl.getShaderInfoLog(fragShader))) {\n\t\tthrow new Error(msg);\n\t}\n\n\tconst id = gl.createProgram();\n\n\tgl.attachShader(id, vertShader);\n\tgl.attachShader(id, fragShader);\n\n\tgl.bindAttribLocation(id, 0, \"a_pos\");\n\tgl.bindAttribLocation(id, 1, \"a_uv\");\n\tgl.bindAttribLocation(id, 2, \"a_color\");\n\n\tgl.linkProgram(id);\n\n\tif ((msg = gl.getProgramInfoLog(id))) {\n\t\t// for some reason on safari it always has a \"\\n\" msg\n\t\tif (msg !== \"\\n\") {\n\t\t\tthrow new Error(msg);\n\t\t}\n\t}\n\n\treturn {\n\n\t\tbind() {\n\t\t\tgl.useProgram(id);\n\t\t},\n\n\t\tunbind() {\n\t\t\tgl.useProgram(null);\n\t\t},\n\n\t\tsend(uniform: Uniform) {\n\t\t\tthis.bind();\n\t\t\tfor (const name in uniform) {\n\t\t\t\tconst val = uniform[name];\n\t\t\t\tconst loc = gl.getUniformLocation(id, name);\n\t\t\t\tif (typeof val === \"number\") {\n\t\t\t\t\tgl.uniform1f(loc, val);\n\t\t\t\t} else if (val instanceof Mat4) {\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tgl.uniformMatrix4fv(loc, false, new Float32Array(val.m));\n\t\t\t\t} else if (val instanceof Color) {\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tgl.uniform4f(loc, val.r, val.g, val.b, val.a);\n\t\t\t\t} else if (val instanceof Vec3) {\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tgl.uniform3f(loc, val.x, val.y, val.z);\n\t\t\t\t} else if (val instanceof Vec2) {\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tgl.uniform2f(loc, val.x, val.y);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.unbind();\n\t\t},\n\n\t};\n\n}\n\nfunction makeFont(\n\ttex: GfxTexture,\n\tgw: number,\n\tgh: number,\n\tchars: string,\n): GfxFont {\n\n\tconst cols = tex.width / gw;\n\tconst rows = tex.height / gh;\n\tconst qw = 1.0 / cols;\n\tconst qh = 1.0 / rows;\n\tconst map: Record = {};\n\tconst charMap = chars.split(\"\").entries();\n\n\tfor (const [i, ch] of charMap) {\n\t\tmap[ch] = vec2(\n\t\t\t(i % cols) * qw,\n\t\t\tMath.floor(i / cols) * qh,\n\t\t);\n\t}\n\n\treturn {\n\t\ttex: tex,\n\t\tmap: map,\n\t\tqw: qw,\n\t\tqh: qh,\n\t};\n\n}\n\n// TODO: expose\nfunction drawRaw(\n\tverts: Vertex[],\n\tindices: number[],\n\tfixed: boolean,\n\ttex: GfxTexture = gfx.defTex,\n\tshader: GfxShader = gfx.defShader,\n\tuniform: Uniform = {},\n) {\n\n\ttex = tex ?? gfx.defTex;\n\tshader = shader ?? gfx.defShader;\n\n\t// flush on texture / shader change and overflow\n\tif (\n\t\ttex !== gfx.curTex\n\t\t|| shader !== gfx.curShader\n\t\t|| !deepEq(gfx.curUniform, uniform)\n\t\t|| gfx.vqueue.length + verts.length * STRIDE > QUEUE_COUNT\n\t\t|| gfx.iqueue.length + indices.length > QUEUE_COUNT\n\t) {\n\t\tflush();\n\t}\n\n\tfor (const v of verts) {\n\n\t\t// TODO: cache camTransform * gfxTransform?\n\t\tconst transform = fixed ? gfx.transform : game.cam.transform.mult(gfx.transform);\n\n\t\t// normalized world space coordinate [-1.0 ~ 1.0]\n\t\tconst pt = screen2ndc(transform.multVec2(v.pos.xy()));\n\n\t\tgfx.vqueue.push(\n\t\t\tpt.x, pt.y, v.pos.z,\n\t\t\tv.uv.x, v.uv.y,\n\t\t\tv.color.r / 255, v.color.g / 255, v.color.b / 255, v.opacity,\n\t\t);\n\n\t}\n\n\tfor (const i of indices) {\n\t\tgfx.iqueue.push(i + gfx.vqueue.length / STRIDE - verts.length);\n\t}\n\n\tgfx.curTex = tex;\n\tgfx.curShader = shader;\n\tgfx.curUniform = uniform;\n\n}\n\n// draw all batched shapes\nfunction flush() {\n\n\tif (\n\t\t!gfx.curTex\n\t\t|| !gfx.curShader\n\t\t|| gfx.vqueue.length === 0\n\t\t|| gfx.iqueue.length === 0\n\t) {\n\t\treturn;\n\t}\n\n\tconst gl = app.gl;\n\n\tgfx.curShader.send(gfx.curUniform);\n\tgl.bindBuffer(gl.ARRAY_BUFFER, gfx.vbuf);\n\tgl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(gfx.vqueue));\n\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gfx.ibuf);\n\tgl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array(gfx.iqueue));\n\tgfx.curShader.bind();\n\tgfx.curTex.bind();\n\tgl.drawElements(gl.TRIANGLES, gfx.iqueue.length, gl.UNSIGNED_SHORT, 0);\n\tgfx.curTex.unbind();\n\tgfx.curShader.unbind();\n\tgl.bindBuffer(gl.ARRAY_BUFFER, null);\n\tgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);\n\n\tgfx.iqueue = [];\n\tgfx.vqueue = [];\n\n\tgfx.drawCalls++;\n\n}\n\n// start a rendering frame, reset some states\nfunction frameStart() {\n\n\tapp.gl.clear(app.gl.COLOR_BUFFER_BIT);\n\n\tif (!gopt.background) {\n\t\tdrawUVQuad({\n\t\t\twidth: width(),\n\t\t\theight: height(),\n\t\t\tquad: new Quad(\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\twidth() * app.scale / BG_GRID_SIZE,\n\t\t\t\theight() * app.scale / BG_GRID_SIZE,\n\t\t\t),\n\t\t\ttex: gfx.bgTex,\n\t\t\tfixed: true,\n\t\t})\n\t}\n\n\tgfx.drawCalls = 0;\n\tgfx.transformStack = [];\n\tgfx.transform = new Mat4();\n\n}\n\nfunction frameEnd() {\n\tflush();\n\tgfx.lastDrawCalls = gfx.drawCalls;\n}\n\nfunction drawCalls() {\n\treturn gfx.lastDrawCalls;\n}\n\n// convert a screen space coordinate to webgl normalized device coordinate\nfunction screen2ndc(pt: Vec2): Vec2 {\n\treturn vec2(\n\t\tpt.x / width() * 2 - 1,\n\t\t-pt.y / height() * 2 + 1,\n\t);\n}\n\n// convert a webgl normalied device coordinate to screen space coordinate\nfunction ndc2screen(pt: Vec2): Vec2 {\n\treturn vec2(\n\t\t(pt.x + 1) / 2 * width(),\n\t\t-(pt.y - 1) / 2 * height(),\n\t);\n}\n\nfunction applyMatrix(m: Mat4) {\n\tgfx.transform = m.clone();\n}\n\nfunction pushTranslate(...args) {\n\tif (args[0] === undefined) return;\n\tconst p = vec2(...args);\n\tif (p.x === 0 && p.y === 0) return;\n\tgfx.transform = gfx.transform.translate(p);\n}\n\nfunction pushScale(...args) {\n\tif (args[0] === undefined) return;\n\tconst p = vec2(...args);\n\tif (p.x === 1 && p.y === 1) return;\n\tgfx.transform = gfx.transform.scale(p);\n}\n\nfunction pushRotateX(a: number) {\n\tif (!a) {\n\t\treturn;\n\t}\n\tgfx.transform = gfx.transform.rotateX(a);\n}\n\nfunction pushRotateY(a: number) {\n\tif (!a) {\n\t\treturn;\n\t}\n\tgfx.transform = gfx.transform.rotateY(a);\n}\n\nfunction pushRotateZ(a: number) {\n\tif (!a) {\n\t\treturn;\n\t}\n\tgfx.transform = gfx.transform.rotateZ(a);\n}\n\nfunction pushTransform() {\n\tgfx.transformStack.push(gfx.transform.clone());\n}\n\nfunction popTransform() {\n\tif (gfx.transformStack.length > 0) {\n\t\tgfx.transform = gfx.transformStack.pop();\n\t}\n}\n\n// draw a uv textured quad\nfunction drawUVQuad(opt: DrawUVQuadOpt) {\n\n\tif (opt.width === undefined || opt.height === undefined) {\n\t\tthrow new Error(\"drawUVQuad() requires property \\\"width\\\" and \\\"height\\\".\");\n\t}\n\n\tif (opt.width <= 0 || opt.height <= 0) {\n\t\treturn;\n\t}\n\n\tconst w = opt.width;\n\tconst h = opt.height;\n\tconst origin = originPt(opt.origin || DEF_ORIGIN);\n\tconst offset = origin.scale(vec2(w, h).scale(-0.5));\n\tconst q = opt.quad || new Quad(0, 0, 1, 1);\n\tconst color = opt.color || rgb(255, 255, 255);\n\tconst opacity = opt.opacity ?? 1;\n\n\tpushTransform();\n\tpushTranslate(opt.pos);\n\tpushRotateZ(opt.angle);\n\tpushScale(opt.scale);\n\tpushTranslate(offset);\n\n\tdrawRaw([\n\t\t{\n\t\t\tpos: vec3(-w / 2, h / 2, 0),\n\t\t\tuv: vec2(opt.flipX ? q.x + q.w : q.x, opt.flipY ? q.y : q.y + q.h),\n\t\t\tcolor: color,\n\t\t\topacity: opacity,\n\t\t},\n\t\t{\n\t\t\tpos: vec3(-w / 2, -h / 2, 0),\n\t\t\tuv: vec2(opt.flipX ? q.x + q.w : q.x, opt.flipY ? q.y + q.h : q.y),\n\t\t\tcolor: color,\n\t\t\topacity: opacity,\n\t\t},\n\t\t{\n\t\t\tpos: vec3(w / 2, -h / 2, 0),\n\t\t\tuv: vec2(opt.flipX ? q.x : q.x + q.w, opt.flipY ? q.y + q.h : q.y),\n\t\t\tcolor: color,\n\t\t\topacity: opacity,\n\t\t},\n\t\t{\n\t\t\tpos: vec3(w / 2, h / 2, 0),\n\t\t\tuv: vec2(opt.flipX ? q.x : q.x + q.w, opt.flipY ? q.y : q.y + q.h),\n\t\t\tcolor: color,\n\t\t\topacity: opacity,\n\t\t},\n\t], [0, 1, 3, 1, 2, 3], opt.fixed, opt.tex, opt.shader, opt.uniform);\n\n\tpopTransform();\n\n}\n\n// TODO: clean\nfunction drawTexture(opt: DrawTextureOpt) {\n\n\tif (!opt.tex) {\n\t\tthrow new Error(\"drawTexture() requires property \\\"tex\\\".\");\n\t}\n\n\tconst q = opt.quad ?? new Quad(0, 0, 1, 1);\n\tconst w = opt.tex.width * q.w;\n\tconst h = opt.tex.height * q.h;\n\tconst scale = vec2(1);\n\n\tif (opt.tiled) {\n\n\t\t// TODO: draw fract\n\t\tconst repX = Math.ceil((opt.width || w) / w);\n\t\tconst repY = Math.ceil((opt.height || h) / h);\n\t\tconst origin = originPt(opt.origin || DEF_ORIGIN).add(vec2(1, 1)).scale(0.5);\n\t\tconst offset = origin.scale(repX * w, repY * h);\n\n\t\t// TODO: rotation\n\t\tfor (let i = 0; i < repX; i++) {\n\t\t\tfor (let j = 0; j < repY; j++) {\n\t\t\t\tdrawUVQuad({\n\t\t\t\t\t...opt,\n\t\t\t\t\tpos: (opt.pos || vec2(0)).add(vec2(w * i, h * j)).sub(offset),\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tscale: scale.scale(opt.scale || vec2(1)),\n\t\t\t\t\ttex: opt.tex,\n\t\t\t\t\tquad: q,\n\t\t\t\t\twidth: w,\n\t\t\t\t\theight: h,\n\t\t\t\t\torigin: \"topleft\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t} else {\n\n\t\t// TODO: should this ignore scale?\n\t\tif (opt.width && opt.height) {\n\t\t\tscale.x = opt.width / w;\n\t\t\tscale.y = opt.height / h;\n\t\t} else if (opt.width) {\n\t\t\tscale.x = opt.width / w;\n\t\t\tscale.y = scale.x;\n\t\t} else if (opt.height) {\n\t\t\tscale.y = opt.height / h;\n\t\t\tscale.x = scale.y;\n\t\t}\n\n\t\tdrawUVQuad({\n\t\t\t...opt,\n\t\t\t// @ts-ignore\n\t\t\tscale: scale.scale(opt.scale || vec2(1)),\n\t\t\ttex: opt.tex,\n\t\t\tquad: q,\n\t\t\twidth: w,\n\t\t\theight: h,\n\t\t});\n\n\t}\n\n}\n\n// TODO: use native asset loader tracking\nconst loading = new Set();\n\nfunction drawSprite(opt: DrawSpriteOpt) {\n\n\tif (!opt.sprite) {\n\t\tthrow new Error(`drawSprite() requires property \"sprite\"`);\n\t}\n\n\tconst spr = findAsset(opt.sprite, assets.sprites);\n\n\tif (!spr) {\n\n\t\t// if passes a source url, we load it implicitly\n\t\tif (typeof opt.sprite === \"string\") {\n\t\t\tif (!loading.has(opt.sprite)) {\n\t\t\t\tloading.add(opt.sprite);\n\t\t\t\tloadSprite(opt.sprite, opt.sprite)\n\t\t\t\t\t.then((a) => loading.delete(opt.sprite));\n\t\t\t}\n\t\t\treturn;\n\t\t} else {\n\t\t\tthrow new Error(`sprite not found: \"${opt.sprite}\"`);\n\t\t}\n\n\t}\n\n\tconst q = spr.frames[opt.frame ?? 0];\n\n\tif (!q) {\n\t\tthrow new Error(`frame not found: ${opt.frame ?? 0}`);\n\t}\n\n\tdrawTexture({\n\t\t...opt,\n\t\ttex: spr.tex,\n\t\tquad: q.scale(opt.quad || new Quad(0, 0, 1, 1)),\n\t\tuniform: opt.uniform,\n\t});\n\n}\n\n// generate vertices to form an arc\nfunction getArcPts(\n\tpos: Vec2,\n\tradiusX: number,\n\tradiusY: number,\n\tstart: number,\n\tend: number,\n\tres: number = 1,\n): Vec2[] {\n\n\t// normalize and turn start and end angles to radians\n\tstart = deg2rad(start % 360);\n\tend = deg2rad(end % 360);\n\tif (end <= start) end += Math.PI * 2;\n\n\t// TODO: better way to get this?\n\t// the number of vertices is sqrt(r1 + r2) * 3 * res with a minimum of 16\n\tconst nverts = Math.ceil(Math.max(Math.sqrt(radiusX + radiusY) * 3 * (res || 1), 16));\n\tconst step = (end - start) / nverts;\n\tconst pts = [];\n\n\t// calculate vertices\n\tfor (let a = start; a < end; a += step) {\n\t\tpts.push(pos.add(radiusX * Math.cos(a), radiusY * Math.sin(a)));\n\t}\n\n\t// doing this on the side due to possible floating point inaccuracy\n\tpts.push(pos.add(radiusX * Math.cos(end), radiusY * Math.sin(end)));\n\n\treturn pts;\n\n}\n\nfunction drawRect(opt: DrawRectOpt) {\n\n\tif (opt.width === undefined || opt.height === undefined) {\n\t\tthrow new Error(\"drawRect() requires property \\\"width\\\" and \\\"height\\\".\");\n\t}\n\n\tif (opt.width <= 0 || opt.height <= 0) {\n\t\treturn;\n\t}\n\n\tconst w = opt.width;\n\tconst h = opt.height;\n\tconst origin = originPt(opt.origin || DEF_ORIGIN).add(1, 1);\n\tconst offset = origin.scale(vec2(w, h).scale(-0.5));\n\n\tlet pts = [\n\t\tvec2(0, 0),\n\t\tvec2(w, 0),\n\t\tvec2(w, h),\n\t\tvec2(0, h),\n\t];\n\n\t// TODO: drawPolygon should handle generic rounded corners\n\tif (opt.radius) {\n\n\t\t// maxium radius is half the shortest side\n\t\tconst r = Math.min(Math.min(w, h) / 2, opt.radius);\n\n\t\tpts = [\n\t\t\tvec2(r, 0),\n\t\t\tvec2(w - r, 0),\n\t\t\t...getArcPts(vec2(w - r, r), r, r, 270, 360),\n\t\t\tvec2(w, r),\n\t\t\tvec2(w, h - r),\n\t\t\t...getArcPts(vec2(w - r, h - r), r, r, 0, 90),\n\t\t\tvec2(w - r, h),\n\t\t\tvec2(r, h),\n\t\t\t...getArcPts(vec2(r, h - r), r, r, 90, 180),\n\t\t\tvec2(0, h - r),\n\t\t\tvec2(0, r),\n\t\t\t...getArcPts(vec2(r, r), r, r, 180, 270),\n\t\t];\n\n\t}\n\n\tdrawPolygon({ ...opt, offset, pts });\n\n}\n\nfunction drawLine(opt: DrawLineOpt) {\n\n\tconst { p1, p2 } = opt;\n\n\tif (!p1 || !p2) {\n\t\tthrow new Error(\"drawLine() requires properties \\\"p1\\\" and \\\"p2\\\".\");\n\t}\n\n\tconst w = opt.width || 1;\n\n\t// the displacement from the line end point to the corner point\n\tconst dis = p2.sub(p1).unit().normal().scale(w * 0.5);\n\n\t// calculate the 4 corner points of the line polygon\n\tconst verts = [\n\t\tp1.sub(dis),\n\t\tp1.add(dis),\n\t\tp2.add(dis),\n\t\tp2.sub(dis),\n\t].map((p) => ({\n\t\tpos: vec3(p.x, p.y, 0),\n\t\tuv: vec2(0),\n\t\tcolor: opt.color ?? Color.WHITE,\n\t\topacity: opt.opacity ?? 1,\n\t}));\n\n\tdrawRaw(verts, [0, 1, 3, 1, 2, 3], opt.fixed, gfx.defTex, opt.shader, opt.uniform);\n\n}\n\nfunction drawLines(opt: DrawLinesOpt) {\n\n\tconst pts = opt.pts;\n\n\tif (!pts) {\n\t\tthrow new Error(\"drawLines() requires property \\\"pts\\\".\");\n\t}\n\n\tif (pts.length < 2) {\n\t\treturn;\n\t}\n\n\tif (opt.radius && pts.length >= 3) {\n\n\t\t// TODO: rounded vertices for arbitury polygonic shape\n\t\tlet minLen = pts[0].dist(pts[1]);\n\n\t\tfor (let i = 1; i < pts.length - 1; i++) {\n\t\t\tminLen = Math.min(pts[i].dist(pts[i + 1]), minLen);\n\t\t}\n\n\t\tconst radius = Math.min(opt.radius, minLen / 2);\n\n\t\tdrawLine({ ...opt, p1: pts[0], p2: pts[1], });\n\n\t\tfor (let i = 1; i < pts.length - 2; i++) {\n\t\t\tconst p1 = pts[i];\n\t\t\tconst p2 = pts[i + 1];\n\t\t\tdrawLine({\n\t\t\t\t...opt,\n\t\t\t\tp1: p1,\n\t\t\t\tp2: p2,\n\t\t\t});\n\t\t}\n\n\t\tdrawLine({ ...opt, p1: pts[pts.length - 2], p2: pts[pts.length - 1], });\n\n\t} else {\n\n\t\tfor (let i = 0; i < pts.length - 1; i++) {\n\t\t\tdrawLine({\n\t\t\t\t...opt,\n\t\t\t\tp1: pts[i],\n\t\t\t\tp2: pts[i + 1],\n\t\t\t});\n\t\t}\n\n\t}\n\n}\n\nfunction drawTriangle(opt: DrawTriangleOpt) {\n\tif (!opt.p1 || !opt.p2 || !opt.p3) {\n\t\tthrow new Error(\"drawPolygon() requires properties \\\"p1\\\", \\\"p2\\\" and \\\"p3\\\".\");\n\t}\n\treturn drawPolygon({\n\t\t...opt,\n\t\tpts: [opt.p1, opt.p2, opt.p3],\n\t});\n}\n\n// TODO: origin\nfunction drawCircle(opt: DrawCircleOpt) {\n\n\tif (!opt.radius) {\n\t\tthrow new Error(\"drawCircle() requires property \\\"radius\\\".\");\n\t}\n\n\tif (opt.radius === 0) {\n\t\treturn;\n\t}\n\n\tdrawEllipse({\n\t\t...opt,\n\t\tradiusX: opt.radius,\n\t\tradiusY: opt.radius,\n\t\tangle: 0,\n\t});\n\n}\n\n// TODO: use fan-like triangulation\nfunction drawEllipse(opt: DrawEllipseOpt) {\n\n\tif (opt.radiusX === undefined || opt.radiusY === undefined) {\n\t\tthrow new Error(\"drawEllipse() requires properties \\\"radiusX\\\" and \\\"radiusY\\\".\");\n\t}\n\n\tif (opt.radiusX === 0 || opt.radiusY === 0) {\n\t\treturn;\n\t}\n\n\tdrawPolygon({\n\t\t...opt,\n\t\tpts: getArcPts(\n\t\t\tvec2(0),\n\t\t\topt.radiusX,\n\t\t\topt.radiusY,\n\t\t\topt.start ?? 0,\n\t\t\topt.end ?? 360,\n\t\t\topt.resolution\n\t\t),\n\t\tradius: 0,\n\t});\n\n}\n\nfunction drawPolygon(opt: DrawPolygonOpt) {\n\n\tif (!opt.pts) {\n\t\tthrow new Error(\"drawPolygon() requires property \\\"pts\\\".\");\n\t}\n\n\tconst npts = opt.pts.length;\n\n\tif (npts < 3) {\n\t\treturn;\n\t}\n\n\tpushTransform();\n\tpushTranslate(opt.pos);\n\tpushScale(opt.scale);\n\tpushRotateZ(opt.angle);\n\tpushTranslate(opt.offset);\n\n\tif (opt.fill !== false) {\n\n\t\tconst color = opt.color ?? Color.WHITE;\n\n\t\tconst verts = opt.pts.map((pt) => ({\n\t\t\tpos: vec3(pt.x, pt.y, 0),\n\t\t\tuv: vec2(0, 0),\n\t\t\tcolor: color,\n\t\t\topacity: opt.opacity ?? 1,\n\t\t}));\n\n\t\t// TODO: better triangulation\n\t\tconst indices = [...Array(npts - 2).keys()]\n\t\t\t.map((n) => [0, n + 1, n + 2])\n\t\t\t.flat();\n\n\t\tdrawRaw(verts, opt.indices ?? indices, opt.fixed, gfx.defTex, opt.shader, opt.uniform);\n\n\t}\n\n\tif (opt.outline) {\n\t\tdrawLines({\n\t\t\tpts: [ ...opt.pts, opt.pts[0] ],\n\t\t\tradius: opt.radius,\n\t\t\twidth: opt.outline.width,\n\t\t\tcolor: opt.outline.color,\n\t\t\tuniform: opt.uniform,\n\t\t\tfixed: opt.fixed,\n\t\t});\n\t}\n\n\tpopTransform();\n\n}\n\nfunction applyCharTransform(fchar: FormattedChar, tr: CharTransform) {\n\tif (tr.pos) fchar.pos = fchar.pos.add(tr.pos);\n\tif (tr.scale) fchar.scale = fchar.scale.scale(vec2(tr.scale));\n\tif (tr.angle) fchar.angle += tr.angle;\n\tif (tr.color) fchar.color = fchar.color.mult(tr.color);\n\tif (tr.opacity) fchar.opacity *= tr.opacity;\n}\n\n// TODO: escape\nconst TEXT_STYLE_RE = /\\[(?[^\\]]*)\\]\\.(?