diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 14568459..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,34 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node -{ - "name": "KAPLAY", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/javascript-node:latest", - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - "settings": {}, - "extensions": [ - "ms-vscode.vscode-typescript-next", - "dprint.dprint" - ] - } - }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [3000], - // Use 'portsAttributes' to set default properties for specific forwarded ports. - // More info: https://containers.dev/implementors/json_reference/#port-attributes - "portsAttributes": { - "8000": { - "label": "Dev/Test Server", - "onAutoForward": "notify" - } - }, - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm update -g && pnpm i" - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..c92a37e0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,28 @@ +name: Publish to NPM + +on: + push: + tags: + - '4000*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + registry-url: "https://registry.npmjs.org" + node-version: 20 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + - name: Install dependencies + run: pnpm install + - name: Publish to NPM + run: npm publish --tag next + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f6f801..4ecabb20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,19 @@ # v4000.0.0 +- Added clipLineToRect - Replaced the Separating Axis Theorem (SAT) with the Gilbert–Johnson–Keerthi (GJK) distance algorithm. - Added circle and (rotated) ellipse collision shapes. -- Added an ellipse component. +- Added `ellipse()` component. - Circle area is no longer a box. -- Added a fake cursor API. - Added restitution and friction. - +- Added a fake cursor API. ```js - const myCursor = add([fakeMouse(), sprite("kat"), pos(100, 100)]); + const myCursor = add([ + fakeMouse(), + sprite("kat"), + pos(100, 100), + ]); myCursor.press(); // trigger onClick events if the mouse is over myCursor.release(); @@ -20,7 +24,7 @@ ## Input -- added input bindings, `onButtonPress`, `onButtonRelease`, `onButtonDown`, and +- Added input bindings, `onButtonPress`, `onButtonRelease`, `onButtonDown`, and it's corresponding boolean versions, `isButtonPressed`, `isButtonDown` and `isButtonReleased`. @@ -63,6 +67,14 @@ }); ``` +- added `pressButton(btn)` and `releaseButton(btn)` to simulate button press and + release + + ```js + pressButton("jump"); // triggers onButtonPress and starts onButtonDown + releaseButton("jump"); // triggers onButtonRelease and stops onButtonDown + ``` + - added the possibility of use arrays in all input handlers ```js @@ -87,7 +99,7 @@ - added `patrol()` component to move along a list of waypoints. - added `sentry()` component to notify when certain objects are in sight. - added `NavMesh` class for pathfinding on a mesh. -- added `navigation()` component to calculate a list of waypoints on a graph. +- added `pathfinder()` component to calculate a list of waypoints on a graph. - now collision checks are only done if there's area objects. ## Game Object @@ -221,9 +233,12 @@ - fix error screen not showing with not Error object +- Added `SpriteComp.animFrame` to get the frame of the current animation (not on + the spritesheet) + ## Audio -- now you can pass an `AudioBuffer` to `loadSound()` (**v4000**) +- now you can pass an `AudioBuffer` to `loadSound()` - added `loadMusic()` to load streaming audio (doesn't block in loading screen). ```js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 128bb295..7621d3c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,10 +31,23 @@ pnpm install # to install dependencies. ## Documentation -Most KAPLAY docs are written on every component file in `src/components`, and -`src/types.ts` as -[jsDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) -above each KAPLAY component entry. +KAPLAY API documentation is auto-generated by JSDoc comments in the source code. + +Normally the definitions of components, types, interfaces (i.e: `SpriteComp`, +`Vec2`, `RectComp`, `LifespanCompOpt`) are on their own files. For example + +- `src/components/draw/sprite.ts` for `SpriteComp` +- `src/math/math.ts` for `Vec2` +- `src/components/draw/rect.ts` for `RectComp` +- `src/components/misc/lifespan.ts` for `LifespanCompOpt` + +Your best option is use **ctrl + click** on the type to go to the definition and +see the JSDoc comments. Other option is to use the search feature of your IDE. + +Types like the components one (`sprite()`, `rect()`, `lifespan()`) are defined +on `types.ts` file, in the `KAPLAYCtx` interface, this is because what +`kaplay()` returns is a `KAPLAYCtx` object. So even if component files has the +definition, the JSDoc comments are on `types.ts`. **Help on improving the documentation is appreciated!** diff --git a/README.md b/README.md index 940acf57..c5c7a8a2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # KAPLAY -![KAPLAY](assets/brand/kaplay-o.webp) +![KAPLAY](/kaplay.webp) [**KAPLAY**](https://kaplayjs.com/) is a JavaScript library that helps you make games fast and fun! diff --git a/examples/add.js b/examples/add.js index e124edb6..7ebceeee 100644 --- a/examples/add.js +++ b/examples/add.js @@ -21,7 +21,7 @@ const player = add([ anchor("center"), // anchor() component defines the pivot point (defaults to "topleft") ]); -// .onUpdate() is a method on all game objects, it registers an event that runs every frame +// .onUpdate() is a method that's found in all game objects, it registers an event that runs every frame player.onUpdate(() => { // .angle is a property provided by rotate() component, here we're incrementing the angle by 120 degrees per second, dt() is the time elapsed since last frame in seconds player.angle += 120 * dt(); diff --git a/examples/ai.js b/examples/ai.js index efdf2a61..00584ba8 100644 --- a/examples/ai.js +++ b/examples/ai.js @@ -2,7 +2,7 @@ // Use state() component to handle basic AI -// Start kaboom +// Start kaplay kaplay(); // Load assets @@ -54,18 +54,21 @@ enemy.onStateEnter("attack", async () => { ]); } + // Waits 1 second to make the enemy enter in "move" state await wait(1); enemy.enterState("move"); }); +// When we enter "move" state, we stay there for 2 sec and then go back to "idle" enemy.onStateEnter("move", async () => { await wait(2); enemy.enterState("idle"); }); -// Like .onUpdate() which runs every frame, but only runs when the current state is "move" -// Here we move towards the player every frame if the current state is "move" +// .onStateUpdate() is similar to .onUpdate(), it'll run every frame, but in this case +// Only when the current state is "move" enemy.onStateUpdate("move", () => { + // We move the enemy in the direction of the player if (!player.exists()) return; const dir = player.pos.sub(enemy.pos).unit(); enemy.move(dir.scale(ENEMY_SPEED)); diff --git a/examples/animation.js b/examples/animation.js index cb041e03..20d3aa4c 100644 --- a/examples/animation.js +++ b/examples/animation.js @@ -1,12 +1,12 @@ // @ts-check -// Start kaboom +// Start kaplay kaplay(); -loadSprite("bean", "sprites/bean.png"); -loadSprite("bag", "sprites/bag.png"); +// We use the default function to load the bean sprite +loadBean(); -// Rotating +// We add a bean that rotates with the animate component const rotatingBean = add([ sprite("bean"), pos(50, 50), @@ -15,15 +15,15 @@ const rotatingBean = add([ animate(), ]); -// Trying sprite change -rotatingBean.sprite = "bag"; - +// We use the 'animate()' function provided by the animate component +// This will rotate the bean from 0 to 360 degrees in 2 seconds +// The direction "forwards" means it will go back to 0 when it ends, which makes this a loop rotatingBean.animate("angle", [0, 360], { duration: 2, direction: "forward", }); -// Moving right to left using ping-pong +// Now we'll move this bean from left to right const movingBean = add([ sprite("bean"), pos(50, 150), @@ -31,6 +31,8 @@ const movingBean = add([ animate(), ]); +// This will animate the bean from left to right in 2 seconds +// The direction "ping-pong" means that when it goes to the right, it will move back to the left movingBean.animate("pos", [vec2(50, 150), vec2(150, 150)], { duration: 2, direction: "ping-pong", @@ -44,12 +46,14 @@ const secondMovingBean = add([ animate({ relative: true }), ]); +// The fact that is relative, means that instead of setting the bean to these positions (vec2(50, 150), vec2(150, 150)) +// It will ADD those positions to the position the bean was spawned in secondMovingBean.animate("pos", [vec2(50, 150), vec2(150, 150)], { duration: 2, direction: "ping-pong", }); -// Changing color using a color list +// We'll change the color of the bean using a list of colors const coloringBean = add([ sprite("bean"), pos(50, 300), @@ -58,11 +62,14 @@ const coloringBean = add([ animate(), ]); +// It will animate the color the bean color from white to red to green to blue to white +// In 8 seconds, and when it's over i'll start over again coloringBean.animate("color", [WHITE, RED, GREEN, BLUE, WHITE], { duration: 8, + direction: "forward", }); -// Changing opacity using an opacity list +// We'll change the opacity of the bean using a list of opacities const opacitingBean = add([ sprite("bean"), pos(150, 300), @@ -71,12 +78,14 @@ const opacitingBean = add([ animate(), ]); +// We'll animate the opacity from 1, to 0, to 1 during 8 seconds +// This time, we'll be using an easing! opacitingBean.animate("opacity", [1, 0, 1], { duration: 8, easing: easings.easeInOutCubic, }); -// Moving in a square like motion +// We'll move this bean in a square shape const squaringBean = add([ sprite("bean"), pos(50, 400), @@ -84,6 +93,7 @@ const squaringBean = add([ animate(), ]); +// Passing an array of keyframes (the positions) it'll move in a square shape squaringBean.animate( "pos", [ @@ -96,7 +106,7 @@ squaringBean.animate( { duration: 8 }, ); -// Moving in a square like motion, but with custom spaced keyframes +// We'll move the bean in a square shape again, but this time we'll be using timing const timedSquaringBean = add([ sprite("bean"), pos(50, 400), @@ -104,6 +114,8 @@ const timedSquaringBean = add([ animate(), ]); +// This will move the bean in the same positions as before in the same time +// But the timings will make the movement from one keyframe to another quicker or slower timedSquaringBean.animate( "pos", [ @@ -125,6 +137,7 @@ timedSquaringBean.animate( }, ); +// We'll move this bean in a curve // Using spline interpolation to move according to a smoothened path const curvingBean = add([ sprite("bean"), @@ -134,6 +147,7 @@ const curvingBean = add([ rotate(0), ]); +// This will move bean in these positions, but using a different interpolation curvingBean.animate( "pos", [ @@ -146,17 +160,20 @@ curvingBean.animate( { duration: 8, direction: "ping-pong", interpolation: "spline" }, ); +// We'll animate a little bean to rotate around the curvingBean! +// Here we're creating a pivot const littleBeanPivot = curvingBean.add([ animate(), rotate(0), - named("littlebeanpivot"), ]); +// And animating the pivot, you know this! littleBeanPivot.animate("angle", [0, 360], { duration: 2, direction: "reverse", }); +// We'll animate a little bean to rotate around the pivot const littleBean = littleBeanPivot.add([ sprite("bean"), pos(50, 50), @@ -164,19 +181,21 @@ const littleBean = littleBeanPivot.add([ scale(0.25), animate(), rotate(0), - named("littlebean"), ]); +// And here we animate the little bean littleBean.animate("angle", [0, 360], { duration: 2, direction: "forward", }); -console.log(JSON.stringify(serializeAnimation(curvingBean, "root"), "", 2)); +// We'll the serialize an animation and log it to the console so we can see all the current animation channels +console.log(JSON.stringify(serializeAnimation(curvingBean, "root"), null, 2)); +// Debug piece of code that draws a line in the curve that the curving bean goes through, don't mind it /*onDraw(() => { drawCurve(t => evaluateCatmullRom( - vec2(200, 400),\ + vec2(200, 400), vec2(250, 500), vec2(300, 400), vec2(350, 500), t), { color: RED }) diff --git a/examples/audio.js b/examples/audio.js index 3ed2ec00..66cc69c2 100644 --- a/examples/audio.js +++ b/examples/audio.js @@ -3,11 +3,12 @@ // audio playback & control kaplay({ - // Don't pause audio when tab is not active + // This makes it so the audio doesn't pause when the tab is changed backgroundAudio: true, background: [0, 0, 0], }); +// Loads the bell sound, and OtherworldlyFoe sound loadSound("bell", "/examples/sounds/bell.mp3"); loadSound("OtherworldlyFoe", "/examples/sounds/OtherworldlyFoe.mp3"); @@ -32,7 +33,7 @@ Time: ${music.time().toFixed(2)} Volume: ${music.volume.toFixed(2)} Speed: ${music.speed.toFixed(2)} -[space] play/pause +\\[space] play/pause [up/down] volume [left/right] speed `.trim(); @@ -51,6 +52,7 @@ onKeyPressRepeat("left", () => music.speed -= 0.1); onKeyPressRepeat("right", () => music.speed += 0.1); onKeyPress("m", () => music.seek(4.24)); +// We store some keys in a string const keyboard = "awsedftgyhujk"; // Simple piano with "bell" sound and the second row of a QWERTY keyboard diff --git a/examples/bench.js b/examples/bench.js index 34e54ad5..1f9140ad 100644 --- a/examples/bench.js +++ b/examples/bench.js @@ -1,22 +1,25 @@ // @ts-config -// bench marking sprite rendering performance +// Bench marking sprite rendering performance +// We use this example to test and bench the performance of kaplay rendering kaplay(); loadSprite("bean", "sprites/bean.png"); loadSprite("bag", "sprites/bag.png"); +// Adds 5 thousand objects which can be a bean or a bag in random positions for (let i = 0; i < 5000; i++) { add([ sprite(i % 2 === 0 ? "bean" : "bag"), pos(rand(0, width()), rand(0, height())), anchor("center"), - ]); + ]).paused = true; } onDraw(() => { drawText({ + // You can get the current fps with debug.fps() text: debug.fps(), pos: vec2(width() / 2, height() / 2), anchor: "center", diff --git a/examples/binding.js b/examples/binding.js index 23a57cea..8a346289 100644 --- a/examples/binding.js +++ b/examples/binding.js @@ -1,12 +1,18 @@ // @ts-check +// You can set the input bindings for your game! kaplay({ buttons: { + // Buttons for jumping "jump": { + // When using a gamepad the button for jumping will be south gamepad: ["south"], + // When using a keyboard the button will be "up" or "w" keyboard: ["up", "w"], + // When using a mouse the button will be "left" mouse: "left", }, + // Buttons for inspecting "inspect": { gamepad: "east", keyboard: "f", @@ -15,7 +21,7 @@ kaplay({ }, }); -loadSprite("bean", "/sprites/bean.png"); +loadBean(); // Set the gravity acceleration (pixels per second) setGravity(1600); @@ -39,20 +45,25 @@ add([ body({ isStatic: true }), ]); +// Adds an object with a text add([ text("Press jump button", { width: width() / 2 }), pos(12, 12), ]); +// This runs when the button for "jump" is pressed (will be on any input device) onButtonPress("jump", () => { + // You can get the type of device that the last input was inputted in! debug.log(getLastInputDeviceType()); + // Now we'll check if the player is on the ground to make it jump if (player.isGrounded()) { // .jump() is provided by body() player.jump(); } }); +// When the button for inspecting is pressed we will log in the debug console for our game the text "inspecting" onButtonDown("inspect", () => { debug.log("inspecting"); }); diff --git a/examples/burp.js b/examples/burp.js index d1865f20..7badf9b6 100644 --- a/examples/burp.js +++ b/examples/burp.js @@ -11,4 +11,4 @@ add([ ]); // burp() on click / tap for our friends on mobile -onClick(burp); +onClick(() => burp()); diff --git a/examples/button.js b/examples/button.js index ef441b91..b686405c 100644 --- a/examples/button.js +++ b/examples/button.js @@ -1,6 +1,6 @@ // @ts-check -// Simple Button UI +// Simple UI and setup for buttons kaplay({ background: [135, 62, 132], @@ -9,7 +9,12 @@ kaplay({ // reset cursor to default on frame start for easier cursor management onUpdate(() => setCursor("default")); -function addButton(txt, p, f) { +// Function that adds a button to the game with a given text, position and function +function addButton( + txt = "start game", + p = vec2(200, 100), + f = () => debug.log("hello"), +) { // add a parent background object const btn = add([ rect(240, 80, { radius: 8 }), @@ -51,5 +56,6 @@ function addButton(txt, p, f) { return btn; } +// Adds the buttons with the function we added addButton("Start", vec2(200, 100), () => debug.log("oh hi")); addButton("Quit", vec2(200, 200), () => debug.log("bye")); diff --git a/examples/camera.js b/examples/camera.js index c1bf9974..0b44e8aa 100644 --- a/examples/camera.js +++ b/examples/camera.js @@ -12,10 +12,12 @@ loadSprite("grass", "/sprites/grass.png"); loadSound("score", "/examples/sounds/score.mp3"); const SPEED = 480; +let score = 0; +// Set the gravity acceleration (pixels per second) setGravity(2400); -// Setup a basic level +// Setup a basic level, check the 'level' example for more info const level = addLevel([ "@ = $", "=======", @@ -49,17 +51,20 @@ const level = addLevel([ // Get the player object from tag const player = level.get("player")[0]; +// Will run every frame player.onUpdate(() => { // Set the viewport center to player.pos camPos(player.worldPos()); }); +// Set the viewport center to player.pos whenever their physics are resolved player.onPhysicsResolve(() => { - // Set the viewport center to player.pos camPos(player.worldPos()); }); +// When the player collides with a coin object player.onCollide("coin", (coin) => { + // It does these things destroy(coin); play("score"); score++; @@ -77,8 +82,6 @@ onKeyPress("space", () => { onKeyDown("left", () => player.move(-SPEED, 0)); onKeyDown("right", () => player.move(SPEED, 0)); -let score = 0; - // Add a ui layer with fixed() component to make the object // not affected by camera const ui = add([ @@ -91,7 +94,7 @@ ui.add([ pos(12), { update() { - this.text = score; + this.text = score.toString(); }, }, ]); diff --git a/examples/children.js b/examples/children.js index 0b9bfc44..235c5a58 100644 --- a/examples/children.js +++ b/examples/children.js @@ -5,6 +5,7 @@ kaplay(); loadSprite("bean", "/sprites/bean.png"); loadSprite("ghosty", "/sprites/ghosty.png"); +// Adds the nucleus for the other children to get added to, it just means this is their parent const nucleus = add([ sprite("ghosty"), pos(center()), @@ -23,6 +24,7 @@ for (let i = 12; i < 24; i++) { ]); } +// Runs every frame nucleus.onUpdate(() => { nucleus.pos = mousePos(); diff --git a/examples/clip.js b/examples/clip.js new file mode 100644 index 00000000..6f706c86 --- /dev/null +++ b/examples/clip.js @@ -0,0 +1,93 @@ +kaplay(); + +const r = new Rect(vec2(100, 100), 300, 200); +const c = new Circle(vec2(250, 200), 100, 100); +const res = new Line(vec2(), vec2()); +const testLines = [ + new Line(vec2(20, 40), vec2(500, 200)), + new Line(vec2(20, 80), vec2(60, 20)), + new Line(vec2(170, 200), vec2(260, 220)), + new Line(vec2(150, 40), vec2(300, 40)), + new Line(vec2(40, 100), vec2(40, 170)), + new Line(vec2(160, 140), vec2(240, 140)), + new Line(vec2(120, 150), vec2(120, 190)), +]; + +function drawRectClippedLine(r, l) { + drawLine({ + p1: l.p1, + p2: l.p2, + color: WHITE, + }); + + if (clipLineToRect(r, l, res)) { + drawLine({ + p1: res.p1, + p2: res.p2, + color: GREEN, + }); + } +} + +function drawCircleClippedLine(r, l) { + drawLine({ + p1: l.p1, + p2: l.p2, + color: WHITE, + }); + + if (clipLineToCircle(c, l, res)) { + drawLine({ + p1: res.p1, + p2: res.p2, + color: GREEN, + }); + } +} + +scene("rect", () => { + onDraw(() => { + drawRect({ + pos: r.pos, + width: r.width, + height: r.height, + fill: false, + outline: { + color: RED, + width: 1, + }, + }); + + for (line of testLines) { + drawRectClippedLine(r, line); + } + }); + + onKeyPress("c", () => { + go("circle"); + }); +}); + +scene("circle", () => { + onDraw(() => { + drawCircle({ + pos: c.center, + radius: c.radius, + fill: false, + outline: { + color: RED, + width: 1, + }, + }); + + for (line of testLines) { + drawCircleClippedLine(c, line); + } + }); + + onKeyPress("r", () => { + go("rect"); + }); +}); + +go("circle"); diff --git a/examples/collision.js b/examples/collision.js index 3d1acb52..895386b2 100644 --- a/examples/collision.js +++ b/examples/collision.js @@ -2,7 +2,7 @@ // Collision handling -// Start kaboom +// Start kaplay kaplay({ scale: 2, }); @@ -121,4 +121,4 @@ player.onUpdate(() => { // Can also be toggled by pressing F1 debug.inspect = true; -// Check out https://kaboomjs.com#AreaComp for everything area() provides +// Check out https://kaplayjs.com/doc/AreaComp/ for everything area() provides diff --git a/examples/collisionshapes.js b/examples/collisionshapes.js index 907506c2..17d4e2f9 100644 --- a/examples/collisionshapes.js +++ b/examples/collisionshapes.js @@ -1,9 +1,12 @@ // @ts-check +// How kaplay handles collisions with different shapes kaplay(); +// Set the gravity acceleration (pixels per second) setGravity(300); +// Adds a ground add([ pos(0, 400), rect(width(), 40), @@ -13,6 +16,7 @@ add([ // Continuous shapes loop(1, () => { + // Adds an object with a random shape add([ pos(width() / 2 + rand(-50, 50), 100), choose([ @@ -26,7 +30,16 @@ loop(1, () => { body(), offscreen({ destroy: true, distance: 10 }), ]); + + // getTreeRoot() gets the root of the game, the object that holds every other object + // This line basically means that if there are more than 20 objects, we destroy the last one if (getTreeRoot().children.length > 20) { destroy(getTreeRoot().children[1]); } + + /* The previous code can also be written as + if (get("*").length > 20) { + destroy(get("*")[1]); + } + */ }); diff --git a/examples/component.js b/examples/component.js index be542f52..47d8b24f 100644 --- a/examples/component.js +++ b/examples/component.js @@ -1,11 +1,12 @@ // @ts-check -// Custom component +// How to make custom components kaplay kaplay(); -loadSprite("bean", "/sprites/bean.png"); +loadBean(); -// Components are just functions that returns an object that follows a certain format +// Components are just function that returns a js object that follows a certain format +// This object contains certain properties which then become available in your object to use function funky() { // Can use local closed variables to store component state let isFunky = false; @@ -14,10 +15,14 @@ function funky() { // ------------------ // Special properties that controls the behavior of the component (all optional) - // The name of the component - id: "funky", - // If this component depend on any other components - require: ["scale", "color"], + // These properties (id and require specially id) are handled by kaplay, id is the name of the component + // If you want to get all objects with this component you can do get("funky") + // Be careful to tag objects with what might be the id of a component + + id: "funky", // The name of the component + require: ["scale", "color"], // If this component depend on any other components + // If the you put components in require and attach this component to an object that doesn't have these components + // The game will throw an error // Runs when the host object is added to the game add() { @@ -28,7 +33,7 @@ function funky() { update() { if (!isFunky) return; - // "this" in all component methods refers to the host game object + // "this" in all component methods refers to the the game object this component is attached to // Here we're updating some properties provided by other components this.color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)); this.scale = vec2(rand(1, 2)); @@ -44,20 +49,23 @@ function funky() { // E.g. Clean up event handlers, etc. }, - // Get the info to present in inspect mode + // When you press F1 you can get a list of inspect properties a component might provide for an object + // Here you can provide custom ones inspect() { - return isFunky ? "on" : "off"; + return "funky: " + isFunky; }, // ------------------ // All other properties and methods are directly assigned to the host object + // This means that the object is getting funky, not that you're getting the property funky lol! getFunky() { isFunky = true; }, }; } +// Adds an object with the funky component const bean = add([ sprite("bean"), pos(center()), @@ -77,7 +85,7 @@ const bean = add([ ]); onKeyPress("space", () => { - // .coolness is from our unnamed component above + // .coolness is from our plain object 'unnamed component' if (bean.coolness >= 100) { // We can use .getFunky() provided by the funky() component now bean.getFunky(); @@ -91,9 +99,11 @@ onKeyPress("r", () => { onKeyPress("escape", () => { // .unuse() removes a component from the game object + // The tag is the one that appears on the id bean.unuse("funky"); }); +// Adds a text object add([ text("Press space to get funky", { width: width() }), pos(12, 12), diff --git a/examples/concert.js b/examples/concert.js index ae0d674a..233d00be 100644 --- a/examples/concert.js +++ b/examples/concert.js @@ -8,13 +8,14 @@ kaplay({ font: "happy", }); +// Adds bean and all of this friends +loadBean(); loadSprite("bag", `/sprites/bag.png`); loadSprite("ghosty", "/sprites/ghosty.png"); loadSprite("bobo", `/sprites/bobo.png`); loadSprite("gigagantrum", "/sprites/gigagantrum.png"); loadSprite("tga", "/sprites/dino.png"); loadSprite("ghostiny", "/sprites/ghostiny.png"); -loadSprite("bean", "/sprites/bean.png"); loadSprite("note", "/sprites/note.png"); loadSprite("grass", "/sprites/grass.png"); loadSprite("cloud", "/sprites/cloud.png"); @@ -23,6 +24,7 @@ loadSound("bell", "/examples/sounds/bell.mp3"); loadSound("kaboom2000", "/examples/sounds/kaboom2000.mp3"); loadBitmapFont("happy", "/examples/fonts/happy_28x36.png", 28, 36); +// An array of friends const friends = [ "bag", "bobo", diff --git a/examples/confetti.js b/examples/confetti.js index 16d35b19..42a72a63 100644 --- a/examples/confetti.js +++ b/examples/confetti.js @@ -1,5 +1,7 @@ // @ts-check +// Confetti effect done manually (not using particle component) + kaplay(); const DEF_COUNT = 80; diff --git a/examples/dialog.js b/examples/dialog.js index 7653ec8c..9063de91 100644 --- a/examples/dialog.js +++ b/examples/dialog.js @@ -15,6 +15,7 @@ kaplay({ font: "happy", }); +// Loads all sprites loadSprite("bean", "/sprites/bean.png"); loadSprite("mark", "/sprites/mark.png"); loadSound("bean_voice", "examples/sounds/bean_voice.wav"); @@ -199,6 +200,7 @@ function startWriting(dialog, char) { }); } +// When the game finishes loading, the dialog will start updating onLoad(() => { updateDialog(); }); diff --git a/examples/doublejump.js b/examples/doublejump.js index b68a4f9d..2815242a 100644 --- a/examples/doublejump.js +++ b/examples/doublejump.js @@ -1,22 +1,25 @@ // @ts-check +// How to use the doubleJump component in this little game kaplay({ background: [141, 183, 255], }); +// Loads sprites loadSprite("bean", "/sprites/bean.png"); loadSprite("coin", "/sprites/coin.png"); loadSprite("grass", "/sprites/grass.png"); loadSprite("spike", "/sprites/spike.png"); loadSound("coin", "/examples/sounds/score.mp3"); +// Set the gravity acceleration (pixels per second) setGravity(4000); const PLAYER_SPEED = 640; const JUMP_FORCE = 1200; const NUM_PLATFORMS = 5; -// a spinning component for fun +// a spinning component for fun, for more info check the 'component' example function spin(speed = 1200) { let spinning = false; return { @@ -37,7 +40,9 @@ function spin(speed = 1200) { }; } +// Setsup the game scene scene("game", () => { + // This score textObject holds a value property in a plain object const score = add([ text("0", { size: 24 }), pos(24, 24), @@ -50,11 +55,13 @@ scene("game", () => { anchor("center"), pos(0, 0), body({ jumpForce: JUMP_FORCE }), + // Adds the double jump component doubleJump(), rotate(0), spin(), ]); + // Adds a num of platforms that go from left to right for (let i = 1; i < NUM_PLATFORMS; i++) { add([ sprite("grass"), @@ -73,6 +80,7 @@ scene("game", () => { // go to the first platform bean.pos = get("platform")[0].pos.sub(0, 64); + // Generates coins on those platforms function genCoin(avoid) { const plats = get("platform"); let idx = randi(0, plats.length); @@ -117,8 +125,9 @@ scene("game", () => { genCoin(c.idx); }); - // spin on double jump + // The double jupm component provides us this function that runs when we double jump bean.onDoubleJump(() => { + // So we can call the spin() method provided by the spin() component to spin bean.spin(); }); @@ -133,6 +142,7 @@ scene("game", () => { bean.doubleJump(); }); + // Will move the bean left and right function move(x) { bean.move(x, 0); if (bean.pos.x < 0) { @@ -152,6 +162,7 @@ scene("game", () => { move(PLAYER_SPEED); }); + // The south button will call the doubleJump, for more info on gamepads check the 'gamepad' example onGamepadButtonPress("south", () => bean.doubleJump()); onGamepadStick("left", (v) => { @@ -175,6 +186,7 @@ scene("game", () => { }); }); +// Sets up the scene where we win scene("win", (score) => { add([ sprite("bean"), @@ -196,6 +208,7 @@ scene("win", (score) => { onGamepadButtonPress("south", () => go("game")); }); +// Sets up the scene where we lose :( scene("lose", () => { add([ text("You Lose"), @@ -204,4 +217,5 @@ scene("lose", () => { onGamepadButtonPress("south", () => go("game")); }); +// Starts the game by entering the game scene go("game"); diff --git a/examples/drag.js b/examples/drag.js index 74a27d1a..ebe70729 100644 --- a/examples/drag.js +++ b/examples/drag.js @@ -49,7 +49,8 @@ onMousePress(() => { if (curDraggin) { return; } - // Loop all "bean"s in reverse, so we pick the topmost one + + // Loop all "bean"s in reverse, so we pick the one that is on top for (const obj of get("drag").reverse()) { // If mouse is pressed and mouse position is inside, we pick if (obj.isHovering()) { diff --git a/examples/draw.js b/examples/draw.js index 3af2bcbe..2cc6435c 100644 --- a/examples/draw.js +++ b/examples/draw.js @@ -1,10 +1,10 @@ // @ts-check -// Kaboom as pure rendering lib (no component / game obj etc.) - +// Kaplay as pure rendering lib (no component / game obj etc.) kaplay(); loadSprite("bean", "/sprites/bean.png"); +// Loads a spiral shader loadShader( "spiral", null, diff --git a/examples/easing.js b/examples/easing.js index 4f5434f1..953949c9 100644 --- a/examples/easing.js +++ b/examples/easing.js @@ -1,5 +1,6 @@ // @ts-check +// Moves objects with custom easings kaplay(); add([ diff --git a/examples/eatlove.js b/examples/eatlove.js index 6574a446..4c0a5513 100644 --- a/examples/eatlove.js +++ b/examples/eatlove.js @@ -2,6 +2,7 @@ kaplay(); +// A lttle game about eating fruit! const fruits = [ "apple", "pineapple", @@ -19,6 +20,7 @@ loadSound("hit", "/examples/sounds/hit.mp3"); loadSound("wooosh", "/examples/sounds/wooosh.mp3"); scene("start", () => { + // Plays the wooosh sound play("wooosh"); add([ diff --git a/examples/egg.js b/examples/egg.js index 225ba585..0a5acac8 100644 --- a/examples/egg.js +++ b/examples/egg.js @@ -1,6 +1,6 @@ // @ts-check -// Egg minigames (yes, like Peppa) +// Egg minigames (yes, like Peppa) kaplay({ background: [135, 62, 132], }); diff --git a/examples/fadeIn.js b/examples/fadeIn.js index a14725a2..e0a6e222 100644 --- a/examples/fadeIn.js +++ b/examples/fadeIn.js @@ -1,5 +1,6 @@ // @ts-check +// How to fade in an object kaplay(); loadBean(); diff --git a/examples/flamebar.js b/examples/flamebar.js index 947f43b0..1ce8b5d3 100644 --- a/examples/flamebar.js +++ b/examples/flamebar.js @@ -2,7 +2,7 @@ // Mario-like flamebar -// Start kaboom +// Start kaplay kaplay(); // Load assets diff --git a/examples/ghosthunting.js b/examples/ghosthunting.js index e7effd7b..963fe143 100644 --- a/examples/ghosthunting.js +++ b/examples/ghosthunting.js @@ -256,8 +256,8 @@ function addEnemy(p) { }), // Patrol can make the enemy follow a computed path patrol({ speed: 100 }), - // Navigator can compute a path given a graph - navigation({ + // Pathfinder can compute a path given a graph + pathfinder({ graph: nav, navigationOpt: { type: "edges", diff --git a/examples/gravity.js b/examples/gravity.js index e5e710ce..35d1dc6e 100644 --- a/examples/gravity.js +++ b/examples/gravity.js @@ -2,7 +2,7 @@ // Responding to gravity & jumping -// Start kaboom +// Start kaplay kaplay(); // Load assets diff --git a/examples/largeTexture.js b/examples/largeTexture.js index 7f5f9c59..cd3f4100 100644 --- a/examples/largeTexture.js +++ b/examples/largeTexture.js @@ -2,12 +2,12 @@ kaplay(); -let cameraPosition = camPos(); -let cameraScale = 1; - // Loads a random 2500px image loadSprite("bigyoshi", "/examples/sprites/YOSHI.png"); +let cameraPosition = camPos(); +let cameraScale = 1; + add([ sprite("bigyoshi"), ]); diff --git a/examples/mazeRaycastedLight.js b/examples/mazeRaycastedLight.js index ac8e05ac..5cbcfad6 100644 --- a/examples/mazeRaycastedLight.js +++ b/examples/mazeRaycastedLight.js @@ -113,6 +113,7 @@ const level = addLevel( "#": () => [ sprite("steel"), tile({ isObstacle: true }), + // area() ], }, }, @@ -143,8 +144,8 @@ onUpdate(() => { const pts = [bean.pos]; // This is overkill, since you theoretically only need to shoot rays to grid positions for (let i = 0; i < 360; i += 1) { - const hit = level.raycast(bean.pos, Vec2.fromAngle(i)); - pts.push(hit.point); + const hit = level.raycast(bean.pos, Vec2.fromAngle(i).scale(64 * 15)); + if (hit) pts.push(hit.point); } pts.push(pts[1]); drawPolygon({ diff --git a/examples/movement.js b/examples/movement.js index 13e2aec1..07c4ae49 100644 --- a/examples/movement.js +++ b/examples/movement.js @@ -2,7 +2,7 @@ // Input handling and basic player movement -// Start kaboom +// Start kaplay kaplay(); // Load assets diff --git a/examples/physicsfactory.js b/examples/physicsfactory.js index b3e4d2cf..c73c7034 100644 --- a/examples/physicsfactory.js +++ b/examples/physicsfactory.js @@ -51,7 +51,7 @@ add([ pos(20, 150), rect(50, 300), area(), - areaEffector({ forceAngle: -90, forceMagnitude: 150 }), + areaEffector({ force: UP.scale(150) }), { draw() { drawPolygon({ diff --git a/examples/platformBox.js b/examples/platformBox.js index 43f87201..f1eef5bc 100644 --- a/examples/platformBox.js +++ b/examples/platformBox.js @@ -46,7 +46,7 @@ const level = addLevel([ if (normal.sub(LEFT).len() < Number.EPSILON) return false; if (normal.sub(RIGHT).len() < Number.EPSILON) return false; return true; - } + }, }), ], }, diff --git a/examples/prettyDebug.js b/examples/prettyDebug.js new file mode 100644 index 00000000..df837326 --- /dev/null +++ b/examples/prettyDebug.js @@ -0,0 +1,17 @@ +kaplay(); + +const pretty = { + i: "am pretty", + all: "own properties are shown", + even: { + nested: "objects", + }, + arrays: ["show", "like", "you", "would", "write", "them"], + "own toString is used": vec2(10, 10), +}; + +debug.log("Text in [brackets] doesn't cause issues"); + +debug.log(pretty); + +debug.error("This is an error message"); diff --git a/examples/raycaster3d.js b/examples/raycaster3d.js index 17251aba..d9130dd7 100644 --- a/examples/raycaster3d.js +++ b/examples/raycaster3d.js @@ -1,6 +1,6 @@ // @ts-check -// Start kaboom +// Start kaplay kaplay(); // load assets diff --git a/examples/textInput.js b/examples/textInput.js index 4a3089b8..28a4d98b 100644 --- a/examples/textInput.js +++ b/examples/textInput.js @@ -1,6 +1,6 @@ // @ts-check -// Start kaboom +// Start kaplay kaplay(); setBackground(BLACK); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 318c8ae1..2350c385 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -14,4 +14,4 @@ "moduleDetection": "force", "noImplicitAny": false } -} \ No newline at end of file +} diff --git a/examples/weirdTextTags.js b/examples/weirdTextTags.js new file mode 100644 index 00000000..956ba41c --- /dev/null +++ b/examples/weirdTextTags.js @@ -0,0 +1,24 @@ +kaplay(); + +const txtEl = add([ + text("", { + styles: { + pink: { + color: MAGENTA, + }, + }, + }), + pos(100, 100), +]); +const base = "[pink]hello\n[/pink]ohhi\n"; +const txt = "foo\n\\[1]\nbar"; +var i = -1; +const c = loop(0.5, () => { + if (txt[i] === "\\") i++; + i++; + txtEl.text = base + txt.slice(0, i) + "[pink]bye[/pink]"; + if (i > txt.length) { + console.log(txtEl.text); + c.cancel(); + } +}); diff --git a/kaplay.webp b/kaplay.webp new file mode 100644 index 00000000..df235072 Binary files /dev/null and b/kaplay.webp differ diff --git a/package.json b/package.json index bdf62861..78631f06 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "kaplay", - "description": "KAPLAY is a JavaScript library that helps you make games fast and fun!", - "version": "4000.0.0-alpha.6", + "description": "KAPLAY is a JavaScript & TypeScript game library that helps you make games fast and fun!", + "version": "4000.0.0-alpha.14", "license": "MIT", - "homepage": "https://kaplayjs.com/", + "homepage": "https://v4000.kaplayjs.com/", "repository": { "type": "git", "url": "git+https://github.com/kaplayjs/kaplay.git" @@ -40,33 +40,32 @@ ], "files": [ "dist/", - "src/", - "kaboom.png", + "kaplay.webp", "CHANGELOG.md" ], "scripts": { "dev": "NODE_ENV=development node scripts/dev.js", "win:dev": "set NODE_ENV=development && node scripts/dev.js", - "build": "node scripts/build.js && npm run mergedoc", + "build": "npm run dts && node scripts/build.js && npm run merge-dts", "check": "tsc", "fmt": "dprint fmt", "test": "node scripts/test.js", - "mergedoc": "dts-bundle-generator -o dist/doc.d.ts dist/declaration/index.d.ts", + "merge-dts": "dts-bundle-generator -o dist/doc.d.ts dist/declaration/index.d.ts", "test:vite": "vitest", "desktop": "tauri dev", "prepare": "npm run build", - "publish:next": "npm publish --tag next" + "publish:next": "npm publish --tag next", + "dts": "tsc --p tsconfig.dts.json" }, "devDependencies": { - "@kaplayjs/dprint-config": "^1.0.0", + "@kaplayjs/dprint-config": "^1.1.0", "dprint": "^0.45.1", "dts-bundle-generator": "^9.5.1", "esbuild": "^0.21.5", - "esbuild-dts-path-alias": "^4.2.1", - "express": "^4.18.3", - "puppeteer": "^22.4.1", - "typescript": "^5.5.3", - "vitest": "^2.0.0" + "express": "^4.21.1", + "puppeteer": "^22.15.0", + "typescript": "^5.6.3", + "vitest": "^2.1.3" }, - "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" + "packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5fe410d..b6f739fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@kaplayjs/dprint-config': - specifier: ^1.0.0 - version: 1.0.0 + specifier: ^1.1.0 + version: 1.1.0 dprint: specifier: ^0.45.1 version: 0.45.1 @@ -20,38 +20,31 @@ importers: esbuild: specifier: ^0.21.5 version: 0.21.5 - esbuild-dts-path-alias: - specifier: ^4.2.1 - version: 4.2.1(esbuild@0.21.5)(typescript@5.5.3) express: - specifier: ^4.18.3 - version: 4.19.2 + specifier: ^4.21.1 + version: 4.21.1 puppeteer: - specifier: ^22.4.1 - version: 22.9.0(typescript@5.5.3) + specifier: ^22.15.0 + version: 22.15.0(typescript@5.6.3) typescript: - specifier: ^5.5.3 - version: 5.5.3 + specifier: ^5.6.3 + version: 5.6.3 vitest: - specifier: ^2.0.0 - version: 2.0.0(@types/node@20.12.12) + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.7.5) packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.24.2': - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.5': - resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.5': - resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} engines: {node: '>=6.9.0'} '@dprint/darwin-arm64@0.45.1': @@ -267,145 +260,139 @@ packages: cpu: [x64] os: [win32] - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@kaplayjs/dprint-config@1.0.0': - resolution: {integrity: sha512-nvP56+acuJisDqIwfAXCsahSnHZNpqKCebBMgZBjouCwIvSqUCl0A3jnKjU7Z9slCwqdE96c7pBMu9k6ofiMXw==} + '@kaplayjs/dprint-config@1.1.0': + resolution: {integrity: sha512-yMcScoE/3cDrVyX+T5I5T2qIMq5pnzdbmTNSTonT3/toOUBQwHqfEsqLjj7+4MkHf16HgNrN8nu+bkZ8VZ36kQ==} + hasBin: true - '@puppeteer/browsers@2.2.3': - resolution: {integrity: sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==} + '@puppeteer/browsers@2.3.0': + resolution: {integrity: sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==} engines: {node: '>=18'} hasBin: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@rollup/rollup-android-arm-eabi@4.24.0': + resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.24.0': + resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.24.0': + resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.24.0': + resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.24.0': + resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.24.0': + resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.24.0': + resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.24.0': + resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.24.0': + resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.24.0': + resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.24.0': + resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.24.0': + resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/node@20.12.12': - resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + '@types/node@22.7.5': + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@vitest/expect@2.0.0': - resolution: {integrity: sha512-5BSfZ0+dAVmC6uPF7s+TcKx0i7oyYHb1WQQL5gg6G2c+Qkaa5BNrdRM74sxDfUIZUgYCr6bfCqmJp+X5bfcNxQ==} + '@vitest/expect@2.1.3': + resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} - '@vitest/runner@2.0.0': - resolution: {integrity: sha512-OovFmlkfRmdhevbWImBUtn9IEM+CKac8O+m9p6W9jTATGVBnDJQ6/jb1gpHyWxsu0ALi5f+TLi+Uyst7AAimMw==} + '@vitest/mocker@2.1.3': + resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} + peerDependencies: + '@vitest/spy': 2.1.3 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.3': + resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} - '@vitest/snapshot@2.0.0': - resolution: {integrity: sha512-B520cSAQwtWgocPpARadnNLslHCxFs5tf7SG2TT96qz+SZgsXqcB1xI3w3/S9kUzdqykEKrMLvW+sIIpMcuUdw==} + '@vitest/runner@2.1.3': + resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} - '@vitest/spy@2.0.0': - resolution: {integrity: sha512-0g7ho4wBK09wq8iNZFtUcQZcUcbPmbLWFotL0GXel0fvk5yPi4nTEKpIvZ+wA5eRyqPUCIfIUl10AWzLr67cmA==} + '@vitest/snapshot@2.1.3': + resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} - '@vitest/utils@2.0.0': - resolution: {integrity: sha512-t0jbx8VugWEP6A29NbyfQKVU68Vo6oUw0iX3a8BwO3nrZuivfHcFO4Y5UsqXlplX+83P9UaqEvC2YQhspC0JSA==} + '@vitest/spy@2.1.3': + resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} + + '@vitest/utils@2.1.3': + resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -427,10 +414,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -445,26 +428,23 @@ packages: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} - b4a@1.6.6: - resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} - bare-events@2.2.2: - resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==} + bare-events@2.5.0: + resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} - bare-fs@2.3.0: - resolution: {integrity: sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==} + bare-fs@2.3.5: + resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} - bare-os@2.3.0: - resolution: {integrity: sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==} + bare-os@2.4.4: + resolution: {integrity: sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==} - bare-path@2.1.2: - resolution: {integrity: sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==} + bare-path@2.1.3: + resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} - bare-stream@1.0.0: - resolution: {integrity: sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==} + bare-stream@2.3.0: + resolution: {integrity: sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -473,13 +453,10 @@ packages: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} - body-parser@1.20.2: - resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -514,8 +491,8 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - chromium-bidi@0.5.19: - resolution: {integrity: sha512-UA6zL77b7RYCjJkZBsZ0wlvCTD+jTjllZ8f6wdO4buevXgTZYjV+XLB9CiEa2OuuTGGTLnI7eN9I60YxuALGQg==} + chromium-bidi@0.6.3: + resolution: {integrity: sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==} peerDependencies: devtools-protocol: '*' @@ -536,8 +513,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -550,8 +528,8 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} cosmiconfig@9.0.0: @@ -563,10 +541,6 @@ packages: typescript: optional: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -579,17 +553,8 @@ packages: supports-color: optional: true - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -617,12 +582,8 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - devtools-protocol@0.0.1286932: - resolution: {integrity: sha512-wu58HMQll9voDjR4NlPyoDEw1syfzaBNHymMMZ/QOXiHRNluOnDgu9hp1yHOKYoMlxCh4lSSiugLITe6Fvu1eA==} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + devtools-protocol@0.0.1312386: + resolution: {integrity: sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==} dprint@0.45.1: resolution: {integrity: sha512-OYefcDgxd6jSdig/Cfkw1vdvyiOIRruCPnqGBbXpc95buDt9kvwL+Lic1OHc+SaQSsQub0BUZMd5+TNgy8Sh3A==} @@ -647,6 +608,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -665,20 +630,13 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - esbuild-dts-path-alias@4.2.1: - resolution: {integrity: sha512-v+i4ognJsL7NlcxJ4QaBWA6Jvj97VVCT6YAa25JKFVsszR4ILTZqrxtd8ryFFJIrhH6LvjEhPztgmj8mFJjbew==} - engines: {node: '>=16.10.0'} - peerDependencies: - esbuild: ^0.17.0 || ^0.18.0 || ^0.21.0 - typescript: '>=5.0' - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-html@1.0.3: @@ -713,12 +671,8 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - express@4.19.2: - resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} + express@4.21.1: + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} extract-zip@2.0.1: @@ -732,8 +686,8 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} forwarded@0.2.0: @@ -760,9 +714,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -771,10 +722,6 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-uri@6.0.3: resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==} engines: {node: '>= 14'} @@ -812,14 +759,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - https-proxy-agent@7.0.4: - resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -849,13 +792,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -875,29 +811,22 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - loupe@3.1.1: - resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -916,22 +845,12 @@ packages: engines: {node: '>=4'} hasBin: true - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -948,12 +867,9 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} @@ -962,12 +878,8 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - pac-proxy-agent@7.0.1: - resolution: {integrity: sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==} + pac-proxy-agent@7.0.2: + resolution: {integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==} engines: {node: '>= 14'} pac-resolver@7.0.1: @@ -986,16 +898,8 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.10: + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1007,17 +911,13 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -1033,20 +933,20 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - puppeteer-core@22.9.0: - resolution: {integrity: sha512-Q2SYVZ1SIE7jCd/Pp+1/mNLFtdJfGvAF+CqOTDG8HcCNCiBvoXfopXfOfMHQ/FueXhGfJW/I6DartWv6QzpNGg==} + puppeteer-core@22.15.0: + resolution: {integrity: sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==} engines: {node: '>=18'} - puppeteer@22.9.0: - resolution: {integrity: sha512-yNux2cm6Sfik4lNLNjJ25Cdn9spJRbMXxl1YZtVZCEhEeej1sFlCvZ/Cr64LhgyJOuvz3iq2uk+RLFpQpGwrjw==} + puppeteer@22.15.0: + resolution: {integrity: sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==} engines: {node: '>=18'} hasBin: true - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} queue-tick@1.0.1: @@ -1060,9 +960,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1071,8 +968,8 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.24.0: + resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1082,17 +979,17 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} set-function-length@1.2.2: @@ -1102,14 +999,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} @@ -1117,24 +1006,20 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socks-proxy-agent@8.0.3: - resolution: {integrity: sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==} + socks-proxy-agent@8.0.4: + resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} engines: {node: '>= 14'} socks@2.8.3: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map@0.6.1: @@ -1154,8 +1039,8 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - streamx@2.16.1: - resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} + streamx@2.20.1: + resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -1165,60 +1050,61 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - tar-fs@3.0.5: - resolution: {integrity: sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==} + tar-fs@3.0.6: + resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==} tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + text-decoder@1.2.0: + resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} - tinypool@1.0.0: - resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} - tinyspy@3.0.0: - resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typescript-transform-paths@3.4.6: - resolution: {integrity: sha512-qdgpCk9oRHkIBhznxaHAapCFapJt5e4FbFik7Y4qdqtp6VyC3smAIPoDEIkjZ2eiF7x5+QxUPYNwJAtw0thsTw==} - peerDependencies: - typescript: '>=3.6.5' - - typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -1239,13 +1125,13 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-node@2.0.0: - resolution: {integrity: sha512-jZtezmjcgZTkMisIi68TdY8w/PqPTxK2pbfTU9/4Gqus1K3AVZqkwH0z7Vshe3CD6mq9rJq8SpqmuefDMIqkfQ==} + vite-node@2.1.3: + resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.3: - resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} + vite@5.4.9: + resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1253,6 +1139,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -1265,6 +1152,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -1272,15 +1161,15 @@ packages: terser: optional: true - vitest@2.0.0: - resolution: {integrity: sha512-NvccE2tZhIoPSq3o3AoTBmItwhHNjzIxvOgfdzILIscyzSGOtw2+A1d/JJbS86HDVbc6TS5HnckQuCgTfp0HDQ==} + vitest@2.1.3: + resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.0.0 - '@vitest/ui': 2.0.0 + '@vitest/browser': 2.1.3 + '@vitest/ui': 2.1.3 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1297,13 +1186,8 @@ packages: jsdom: optional: true - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true @@ -1314,8 +1198,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1330,9 +1214,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1344,29 +1225,24 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} snapshots: - '@ampproject/remapping@2.3.0': + '@babel/code-frame@7.25.7': dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 + '@babel/highlight': 7.25.7 + picocolors: 1.1.0 - '@babel/code-frame@7.24.2': - dependencies: - '@babel/highlight': 7.24.5 - picocolors: 1.0.1 + '@babel/helper-validator-identifier@7.25.7': {} - '@babel/helper-validator-identifier@7.24.5': {} - - '@babel/highlight@7.24.5': + '@babel/highlight@7.25.7': dependencies: - '@babel/helper-validator-identifier': 7.24.5 + '@babel/helper-validator-identifier': 7.25.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@dprint/darwin-arm64@0.45.1': optional: true @@ -1482,135 +1358,127 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@kaplayjs/dprint-config@1.0.0': + '@kaplayjs/dprint-config@1.1.0': dependencies: + commander: 12.1.0 dprint: 0.47.2 - '@puppeteer/browsers@2.2.3': + '@puppeteer/browsers@2.3.0': dependencies: - debug: 4.3.4 + debug: 4.3.7 extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.4.0 - semver: 7.6.0 - tar-fs: 3.0.5 + semver: 7.6.3 + tar-fs: 3.0.6 unbzip2-stream: 1.4.3 yargs: 17.7.2 transitivePeerDependencies: - supports-color - '@rollup/rollup-android-arm-eabi@4.18.0': + '@rollup/rollup-android-arm-eabi@4.24.0': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-darwin-arm64@4.24.0': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-x64@4.24.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.24.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.24.0': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-x64-musl@4.24.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-win32-arm64-msvc@4.24.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-win32-ia32-msvc@4.24.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true - '@sinclair/typebox@0.27.8': {} - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} - '@types/node@20.12.12': + '@types/node@22.7.5': dependencies: - undici-types: 5.26.5 + undici-types: 6.19.8 optional: true '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.12.12 + '@types/node': 22.7.5 optional: true - '@vitest/expect@2.0.0': + '@vitest/expect@2.1.3': dependencies: - '@vitest/spy': 2.0.0 - '@vitest/utils': 2.0.0 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 + tinyrainbow: 1.2.0 - '@vitest/runner@2.0.0': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.9(@types/node@22.7.5))': dependencies: - '@vitest/utils': 2.0.0 + '@vitest/spy': 2.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.12 + optionalDependencies: + vite: 5.4.9(@types/node@22.7.5) + + '@vitest/pretty-format@2.1.3': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.3': + dependencies: + '@vitest/utils': 2.1.3 pathe: 1.1.2 - '@vitest/snapshot@2.0.0': + '@vitest/snapshot@2.1.3': dependencies: - magic-string: 0.30.10 + '@vitest/pretty-format': 2.1.3 + magic-string: 0.30.12 pathe: 1.1.2 - pretty-format: 29.7.0 - '@vitest/spy@2.0.0': + '@vitest/spy@2.1.3': dependencies: - tinyspy: 3.0.0 + tinyspy: 3.0.2 - '@vitest/utils@2.0.0': + '@vitest/utils@2.1.3': dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 3.1.1 - pretty-format: 29.7.0 + '@vitest/pretty-format': 2.1.3 + loupe: 3.1.2 + tinyrainbow: 1.2.0 accepts@1.3.8: dependencies: @@ -1619,7 +1487,7 @@ snapshots: agent-base@7.1.1: dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -1633,8 +1501,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - argparse@2.0.1: {} array-flatten@1.1.1: {} @@ -1643,40 +1509,39 @@ snapshots: ast-types@0.13.4: dependencies: - tslib: 2.6.2 - - b4a@1.6.6: {} + tslib: 2.7.0 - balanced-match@1.0.2: {} + b4a@1.6.7: {} - bare-events@2.2.2: + bare-events@2.5.0: optional: true - bare-fs@2.3.0: + bare-fs@2.3.5: dependencies: - bare-events: 2.2.2 - bare-path: 2.1.2 - bare-stream: 1.0.0 + bare-events: 2.5.0 + bare-path: 2.1.3 + bare-stream: 2.3.0 optional: true - bare-os@2.3.0: + bare-os@2.4.4: optional: true - bare-path@2.1.2: + bare-path@2.1.3: dependencies: - bare-os: 2.3.0 + bare-os: 2.4.4 optional: true - bare-stream@1.0.0: + bare-stream@2.3.0: dependencies: - streamx: 2.16.1 + b4a: 1.6.7 + streamx: 2.20.1 optional: true base64-js@1.5.1: {} basic-ftp@5.0.5: {} - body-parser@1.20.2: + body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -1686,18 +1551,13 @@ snapshots: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 + qs: 6.13.0 raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - buffer-crc32@0.2.13: {} buffer@5.7.1: @@ -1724,7 +1584,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.1 + loupe: 3.1.2 pathval: 2.0.0 chalk@2.4.2: @@ -1735,12 +1595,12 @@ snapshots: check-error@2.1.1: {} - chromium-bidi@0.5.19(devtools-protocol@0.0.1286932): + chromium-bidi@0.6.3(devtools-protocol@0.0.1312386): dependencies: - devtools-protocol: 0.0.1286932 + devtools-protocol: 0.0.1312386 mitt: 3.0.1 urlpattern-polyfill: 10.0.0 - zod: 3.22.4 + zod: 3.23.8 cliui@8.0.1: dependencies: @@ -1760,7 +1620,7 @@ snapshots: color-name@1.1.4: {} - concat-map@0.0.1: {} + commander@12.1.0: {} content-disposition@0.5.4: dependencies: @@ -1770,22 +1630,16 @@ snapshots: cookie-signature@1.0.6: {} - cookie@0.6.0: {} + cookie@0.7.1: {} - cosmiconfig@9.0.0(typescript@5.5.3): + cosmiconfig@9.0.0(typescript@5.6.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.5.3 - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + typescript: 5.6.3 data-uri-to-buffer@6.0.2: {} @@ -1793,13 +1647,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.4: + debug@4.3.7: dependencies: - ms: 2.1.2 - - debug@4.3.5: - dependencies: - ms: 2.1.2 + ms: 2.1.3 deep-eql@5.0.2: {} @@ -1819,9 +1669,7 @@ snapshots: destroy@1.2.0: {} - devtools-protocol@0.0.1286932: {} - - diff-sequences@29.6.3: {} + devtools-protocol@0.0.1312386: {} dprint@0.45.1: optionalDependencies: @@ -1846,7 +1694,7 @@ snapshots: dts-bundle-generator@9.5.1: dependencies: - typescript: 5.5.3 + typescript: 5.6.3 yargs: 17.7.2 ee-first@1.1.1: {} @@ -1855,6 +1703,8 @@ snapshots: encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -1871,12 +1721,6 @@ snapshots: es-errors@1.3.0: {} - esbuild-dts-path-alias@4.2.1(esbuild@0.21.5)(typescript@5.5.3): - dependencies: - esbuild: 0.21.5 - typescript: 5.5.3 - typescript-transform-paths: 3.4.6(typescript@5.5.3) - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -1903,7 +1747,7 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.1.2: {} + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -1923,52 +1767,40 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} etag@1.8.1: {} - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - express@4.19.2: + express@4.21.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.2 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.6.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.10 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -1979,7 +1811,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.7 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -1993,10 +1825,10 @@ snapshots: dependencies: pend: 1.2.0 - finalhandler@1.2.0: + finalhandler@1.3.1: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 @@ -2022,8 +1854,6 @@ snapshots: get-caller-file@2.0.5: {} - get-func-name@2.0.2: {} - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -2034,15 +1864,13 @@ snapshots: get-stream@5.2.0: dependencies: - pump: 3.0.0 - - get-stream@8.0.1: {} + pump: 3.0.2 get-uri@6.0.3: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 fs-extra: 11.2.0 transitivePeerDependencies: - supports-color @@ -2078,19 +1906,17 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.4: + https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color - human-signals@5.0.0: {} - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -2115,10 +1941,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-stream@3.0.0: {} - - isexe@2.0.0: {} - js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -2137,25 +1959,17 @@ snapshots: lines-and-columns@1.2.4: {} - loupe@3.1.1: - dependencies: - get-func-name: 2.0.2 - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 + loupe@3.1.2: {} lru-cache@7.18.3: {} - magic-string@0.30.10: + magic-string@0.30.12: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 media-typer@0.3.0: {} - merge-descriptors@1.0.1: {} - - merge-stream@2.0.0: {} + merge-descriptors@1.0.3: {} methods@1.1.2: {} @@ -2167,18 +1981,10 @@ snapshots: mime@1.6.0: {} - mimic-fn@4.0.0: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - mitt@3.0.1: {} ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} nanoid@3.3.7: {} @@ -2187,11 +1993,7 @@ snapshots: netmask@2.0.2: {} - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - object-inspect@1.13.1: {} + object-inspect@1.13.2: {} on-finished@2.4.1: dependencies: @@ -2201,20 +2003,16 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - pac-proxy-agent@7.0.1: + pac-proxy-agent@7.0.2: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 get-uri: 6.0.3 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.4 + https-proxy-agent: 7.0.5 pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.3 + socks-proxy-agent: 8.0.4 transitivePeerDependencies: - supports-color @@ -2229,18 +2027,14 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.2 + '@babel/code-frame': 7.25.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parseurl@1.3.3: {} - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-to-regexp@0.1.7: {} + path-to-regexp@0.1.10: {} pathe@1.1.2: {} @@ -2248,19 +2042,13 @@ snapshots: pend@1.2.0: {} - picocolors@1.0.1: {} + picocolors@1.1.0: {} - postcss@8.4.39: + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 + picocolors: 1.1.0 + source-map-js: 1.2.1 progress@2.0.3: {} @@ -2272,48 +2060,48 @@ snapshots: proxy-agent@6.4.0: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.4 + https-proxy-agent: 7.0.5 lru-cache: 7.18.3 - pac-proxy-agent: 7.0.1 + pac-proxy-agent: 7.0.2 proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.3 + socks-proxy-agent: 8.0.4 transitivePeerDependencies: - supports-color proxy-from-env@1.1.0: {} - pump@3.0.0: + pump@3.0.2: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - puppeteer-core@22.9.0: + puppeteer-core@22.15.0: dependencies: - '@puppeteer/browsers': 2.2.3 - chromium-bidi: 0.5.19(devtools-protocol@0.0.1286932) - debug: 4.3.4 - devtools-protocol: 0.0.1286932 - ws: 8.17.0 + '@puppeteer/browsers': 2.3.0 + chromium-bidi: 0.6.3(devtools-protocol@0.0.1312386) + debug: 4.3.7 + devtools-protocol: 0.0.1312386 + ws: 8.18.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - puppeteer@22.9.0(typescript@5.5.3): + puppeteer@22.15.0(typescript@5.6.3): dependencies: - '@puppeteer/browsers': 2.2.3 - cosmiconfig: 9.0.0(typescript@5.5.3) - devtools-protocol: 0.0.1286932 - puppeteer-core: 22.9.0 + '@puppeteer/browsers': 2.3.0 + cosmiconfig: 9.0.0(typescript@5.6.3) + devtools-protocol: 0.0.1312386 + puppeteer-core: 22.15.0 transitivePeerDependencies: - bufferutil - supports-color - typescript - utf-8-validate - qs@6.11.0: + qs@6.13.0: dependencies: side-channel: 1.0.6 @@ -2328,43 +2116,39 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-is@18.3.1: {} - require-directory@2.1.1: {} resolve-from@4.0.0: {} - rollup@4.18.0: + rollup@4.24.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.24.0 + '@rollup/rollup-android-arm64': 4.24.0 + '@rollup/rollup-darwin-arm64': 4.24.0 + '@rollup/rollup-darwin-x64': 4.24.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 + '@rollup/rollup-linux-arm-musleabihf': 4.24.0 + '@rollup/rollup-linux-arm64-gnu': 4.24.0 + '@rollup/rollup-linux-arm64-musl': 4.24.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 + '@rollup/rollup-linux-riscv64-gnu': 4.24.0 + '@rollup/rollup-linux-s390x-gnu': 4.24.0 + '@rollup/rollup-linux-x64-gnu': 4.24.0 + '@rollup/rollup-linux-x64-musl': 4.24.0 + '@rollup/rollup-win32-arm64-msvc': 4.24.0 + '@rollup/rollup-win32-ia32-msvc': 4.24.0 + '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} - semver@7.6.0: - dependencies: - lru-cache: 6.0.0 + semver@7.6.3: {} - send@0.18.0: + send@0.19.0: dependencies: debug: 2.6.9 depd: 2.0.0 @@ -2382,12 +2166,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.15.0: + serve-static@1.16.2: dependencies: - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.18.0 + send: 0.19.0 transitivePeerDependencies: - supports-color @@ -2402,29 +2186,21 @@ snapshots: setprototypeof@1.2.0: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - side-channel@1.0.6: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.1 + object-inspect: 1.13.2 siginfo@2.0.0: {} - signal-exit@4.1.0: {} - smart-buffer@4.2.0: {} - socks-proxy-agent@8.0.3: + socks-proxy-agent@8.0.4: dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.7 socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -2434,7 +2210,7 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map@0.6.1: optional: true @@ -2447,12 +2223,13 @@ snapshots: std-env@3.7.0: {} - streamx@2.16.1: + streamx@2.20.1: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 + text-decoder: 1.2.0 optionalDependencies: - bare-events: 2.2.2 + bare-events: 2.5.0 string-width@4.2.3: dependencies: @@ -2464,56 +2241,57 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-final-newline@3.0.0: {} - supports-color@5.5.0: dependencies: has-flag: 3.0.0 - tar-fs@3.0.5: + tar-fs@3.0.6: dependencies: - pump: 3.0.0 + pump: 3.0.2 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 2.3.0 - bare-path: 2.1.2 + bare-fs: 2.3.5 + bare-path: 2.1.3 tar-stream@3.1.7: dependencies: - b4a: 1.6.6 + b4a: 1.6.7 fast-fifo: 1.3.2 - streamx: 2.16.1 + streamx: 2.20.1 + + text-decoder@1.2.0: + dependencies: + b4a: 1.6.7 through@2.3.8: {} - tinybench@2.8.0: {} + tinybench@2.9.0: {} + + tinyexec@0.3.0: {} - tinypool@1.0.0: {} + tinypool@1.0.1: {} - tinyspy@3.0.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} toidentifier@1.0.1: {} - tslib@2.6.2: {} + tslib@2.7.0: {} type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - typescript-transform-paths@3.4.6(typescript@5.5.3): - dependencies: - minimatch: 3.1.2 - typescript: 5.5.3 - - typescript@5.5.3: {} + typescript@5.6.3: {} unbzip2-stream@1.4.3: dependencies: buffer: 5.7.1 through: 2.3.8 - undici-types@5.26.5: + undici-types@6.19.8: optional: true universalify@2.0.1: {} @@ -2526,68 +2304,67 @@ snapshots: vary@1.1.2: {} - vite-node@2.0.0(@types/node@20.12.12): + vite-node@2.1.3(@types/node@22.7.5): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.3.7 pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.12.12) + vite: 5.4.9(@types/node@22.7.5) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite@5.3.3(@types/node@20.12.12): + vite@5.4.9(@types/node@22.7.5): dependencies: esbuild: 0.21.5 - postcss: 8.4.39 - rollup: 4.18.0 + postcss: 8.4.47 + rollup: 4.24.0 optionalDependencies: - '@types/node': 20.12.12 + '@types/node': 22.7.5 fsevents: 2.3.3 - vitest@2.0.0(@types/node@20.12.12): + vitest@2.1.3(@types/node@22.7.5): dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.0 - '@vitest/runner': 2.0.0 - '@vitest/snapshot': 2.0.0 - '@vitest/spy': 2.0.0 - '@vitest/utils': 2.0.0 + '@vitest/expect': 2.1.3 + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.9(@types/node@22.7.5)) + '@vitest/pretty-format': 2.1.3 + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 - debug: 4.3.5 - execa: 8.0.1 - magic-string: 0.30.10 + debug: 4.3.7 + magic-string: 0.30.12 pathe: 1.1.2 - picocolors: 1.0.1 std-env: 3.7.0 - tinybench: 2.8.0 - tinypool: 1.0.0 - vite: 5.3.3(@types/node@20.12.12) - vite-node: 2.0.0(@types/node@20.12.12) - why-is-node-running: 2.2.2 + tinybench: 2.9.0 + tinyexec: 0.3.0 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.9(@types/node@22.7.5) + vite-node: 2.1.3(@types/node@22.7.5) + why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.12.12 + '@types/node': 22.7.5 transitivePeerDependencies: - less - lightningcss + - msw - sass + - sass-embedded - stylus - sugarss - supports-color - terser - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 @@ -2600,18 +2377,16 @@ snapshots: wrappy@1.0.2: {} - ws@8.17.0: {} + ws@8.18.0: {} y18n@5.0.8: {} - yallist@4.0.0: {} - yargs-parser@21.1.1: {} yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -2623,4 +2398,4 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - zod@3.22.4: {} + zod@3.23.8: {} diff --git a/scripts/lib.js b/scripts/lib.js index 07b129dd..36d38186 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -1,6 +1,5 @@ import cp from "child_process"; import * as esbuild from "esbuild"; -import { dTSPathAliasPlugin } from "esbuild-dts-path-alias"; import express from "express"; import fs from "fs/promises"; import path from "path"; @@ -75,21 +74,6 @@ async function writeFile(path, content) { } export async function genDTS() { - // generate declaration files - esbuild.build({ - bundle: true, - target: "esnext", - format: "esm", - entryPoints: ["./src/index.ts"], - outdir: "./dist/declaration/", - plugins: [dTSPathAliasPlugin()], - loader: { - ".png": "dataurl", - ".glsl": "text", - ".mp3": "binary", - }, - }); - // global dts const dts = await fs.readFile(`${srcDir}/types.ts`, "utf-8"); diff --git a/src/app/app.ts b/src/app/app.ts index d38e49da..9daee451 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -20,7 +20,7 @@ import { setHasOrIncludes, } from "../utils"; -import GAMEPAD_MAP from "../data/gamepad.json"; +import GAMEPAD_MAP from "../data/gamepad.json" assert { type: "json" }; import { gfx } from "../kaplay"; import { type ButtonBinding, @@ -81,6 +81,8 @@ export type AppState = ReturnType; export let appState: AppState; +const GP_MAP = GAMEPAD_MAP as Record; + export const initAppState = (opt: { canvas: HTMLCanvasElement; touchToMouse?: boolean; @@ -446,6 +448,16 @@ export const initApp = (opt: { }; } + function pressButton(btn: string) { + state.buttonState.press(btn); + state.events.trigger("buttonPress", btn); + } + + function releaseButton(btn: string) { + state.buttonState.release(btn); + state.events.trigger("buttonRelease", btn); + } + function onResize(action: () => void): KEventController { return state.events.on("resize", action); } @@ -621,11 +633,11 @@ export const initApp = (opt: { } function onGamepadConnect(action: (gamepad: KGamepad) => void) { - state.events.on("gamepadConnect", action); + return state.events.on("gamepadConnect", action); } function onGamepadDisconnect(action: (gamepad: KGamepad) => void) { - state.events.on("gamepadDisconnect", action); + return state.events.on("gamepadDisconnect", action); } function getGamepadStick(stick: GamepadStick): Vec2 { @@ -673,11 +685,7 @@ export const initApp = (opt: { state.mouseState.down.forEach((k) => state.events.trigger("mouseDown", k) ); - state.buttonState.down.forEach((btn) => { - const gamepadBindings = getButton(btn)?.gamepad; - if (gamepadBindings && isGamepadButtonDown(gamepadBindings)) return; - state.events.trigger("buttonDown", btn); }); @@ -765,10 +773,10 @@ export const initApp = (opt: { for (const gamepad of state.gamepads) { const browserGamepad = navigator.getGamepads()[gamepad.index]; if (!browserGamepad) continue; + const customMap = opt.gamepads ?? {}; const map = customMap[browserGamepad.id] - ?? (GAMEPAD_MAP as Record)[browserGamepad.id] - ?? GAMEPAD_MAP["default"]; + || GP_MAP[browserGamepad.id] || GP_MAP["default"]; const gamepadState = state.gamepadStates.get(gamepad.index); if (!gamepadState) continue; @@ -780,39 +788,32 @@ export const initApp = (opt: { ); if (browserGamepadBtn.pressed) { - if (!gamepadState.buttonState.down.has(gamepadBtn)) { - state.lastInputDevice = "gamepad"; - - if (isGamepadButtonBind) { - // replicate input in merged state, defined button state and gamepad state - state.buttonsByGamepad.get(gamepadBtn)?.forEach( - (btn) => { - state.buttonState.press(btn); - state.events.trigger("buttonPress", btn); - }, - ); - } - - state.mergedGamepadState.buttonState.press(gamepadBtn); - gamepadState.buttonState.press(gamepadBtn); + if (gamepadState.buttonState.down.has(gamepadBtn)) { state.events.trigger( - "gamepadButtonPress", + "gamepadButtonDown", gamepadBtn, gamepad, ); + + continue; } + state.lastInputDevice = "gamepad"; + if (isGamepadButtonBind) { + // replicate input in merged state, defined button state and gamepad state state.buttonsByGamepad.get(gamepadBtn)?.forEach( (btn) => { state.buttonState.press(btn); - state.events.trigger("buttonDown", btn); + state.events.trigger("buttonPress", btn); }, ); } + state.mergedGamepadState.buttonState.press(gamepadBtn); + gamepadState.buttonState.press(gamepadBtn); state.events.trigger( - "gamepadButtonDown", + "gamepadButtonPress", gamepadBtn, gamepad, ); @@ -1266,6 +1267,8 @@ export const initApp = (opt: { isButtonReleased, setButton, getButton, + pressButton, + releaseButton, charInputted, onResize, onKeyDown, diff --git a/src/assets/asset.ts b/src/assets/asset.ts index 42bdf1d4..ae34d7bb 100644 --- a/src/assets/asset.ts +++ b/src/assets/asset.ts @@ -1,6 +1,6 @@ import { SPRITE_ATLAS_HEIGHT, SPRITE_ATLAS_WIDTH } from "../constants"; +import TexPacker from "../gfx/classes/TexPacker"; import type { GfxCtx } from "../gfx/gfx"; -import TexPacker from "../gfx/texPacker"; import { assets } from "../kaplay"; import { KEvent } from "../utils"; import type { BitmapFontData } from "./bitmapFont"; @@ -11,6 +11,8 @@ import type { SpriteData } from "./sprite"; /** * An asset is a resource that is loaded asynchronously. + * + * It can be a sprite, a sound, a font, a shader, etc. */ export class Asset { loaded: boolean = false; @@ -19,6 +21,7 @@ export class Asset { private onLoadEvents: KEvent<[D]> = new KEvent(); private onErrorEvents: KEvent<[Error]> = new KEvent(); private onFinishEvents: KEvent<[]> = new KEvent(); + constructor(loader: Promise) { loader.then((data) => { this.loaded = true; @@ -26,6 +29,7 @@ export class Asset { this.onLoadEvents.trigger(data); }).catch((err) => { this.error = err; + if (this.onErrorEvents.numListeners() > 0) { this.onErrorEvents.trigger(err); } @@ -84,17 +88,20 @@ export class Asset { export class AssetBucket { assets: Map> = new Map(); lastUID: number = 0; + add(name: string | null, loader: Promise): Asset { // if user don't provide a name we use a generated one const id = name ?? (this.lastUID++ + ""); const asset = new Asset(loader); this.assets.set(id, asset); + return asset; } addLoaded(name: string | null, data: D): Asset { const id = name ?? (this.lastUID++ + ""); const asset = Asset.loaded(data); this.assets.set(id, asset); + return asset; } // if not found return undefined @@ -106,21 +113,22 @@ export class AssetBucket { return 1; } let loaded = 0; + this.assets.forEach((asset) => { if (asset.loaded) { loaded++; } }); + return loaded / this.assets.size; } } export function fetchURL(url: string) { - return fetch(url) - .then((res) => { - if (!res.ok) throw new Error(`Failed to fetch "${url}"`); - return res; - }); + return fetch(url).then((res) => { + if (!res.ok) throw new Error(`Failed to fetch "${url}"`); + return res; + }); } export function fetchJSON(path: string) { @@ -148,10 +156,11 @@ export function loadJSON(name: string, url: string) { } // wrapper around image loader to get a Promise -export function loadImg(src: string): Promise { +export function loadImage(src: string): Promise { const img = new Image(); img.crossOrigin = "anonymous"; img.src = src; + return new Promise((resolve, reject) => { img.onload = () => resolve(img); img.onerror = () => diff --git a/src/assets/bitmapFont.ts b/src/assets/bitmapFont.ts index a1a9ddf6..13cdbf20 100644 --- a/src/assets/bitmapFont.ts +++ b/src/assets/bitmapFont.ts @@ -3,7 +3,7 @@ import { Texture } from "../gfx"; import { assets, gfx } from "../kaplay"; import type { Quad } from "../math/math"; import type { TexFilter } from "../types"; -import { type Asset, loadImg } from "./asset"; +import { type Asset, loadImage } from "./asset"; import { makeFont } from "./font"; export interface GfxFont { @@ -35,7 +35,7 @@ export function loadBitmapFont( ): Asset { return assets.bitmapFonts.add( name, - loadImg(src) + loadImage(src) .then((img) => { return makeFont( Texture.fromImage(gfx.ggl, img, opt), diff --git a/src/assets/pedit.ts b/src/assets/pedit.ts index d0b653b7..adc5888d 100644 --- a/src/assets/pedit.ts +++ b/src/assets/pedit.ts @@ -1,5 +1,5 @@ import { assets } from "../kaplay"; -import { type Asset, fetchJSON, loadImg } from "./asset"; +import { type Asset, fetchJSON, loadImage } from "./asset"; import { loadSprite, type SpriteAnims, type SpriteData } from "./sprite"; import { fixURL } from "./utils"; @@ -22,7 +22,7 @@ export function loadPedit( const data = typeof src === "string" ? await fetchJSON(src) : src; - const images = await Promise.all(data.frames.map(loadImg)); + const images = await Promise.all(data.frames.map(loadImage)); const canvas = document.createElement("canvas"); canvas.width = data.width; canvas.height = data.height * data.frames.length; diff --git a/src/assets/sprite.ts b/src/assets/sprite.ts index d69f8bd6..7d5e30d2 100644 --- a/src/assets/sprite.ts +++ b/src/assets/sprite.ts @@ -1,4 +1,4 @@ -import { Asset, loadImg, loadProgress } from "../assets"; +import { Asset, loadImage, loadProgress } from "../assets"; import type { DrawSpriteOpt } from "../gfx"; import type { Texture } from "../gfx/gfx"; import { assets } from "../kaplay"; @@ -95,17 +95,20 @@ export class SpriteData { frames: Quad[] = [new Quad(0, 0, 1, 1)]; anims: SpriteAnims = {}; slice9: NineSlice | null = null; + packerId: number | null; constructor( tex: Texture, frames?: Quad[], anims: SpriteAnims = {}, slice9: NineSlice | null = null, + packerId: number | null = null, ) { this.tex = tex; if (frames) this.frames = frames; this.anims = anims; this.slice9 = slice9; + this.packerId = packerId; } /** @@ -132,7 +135,7 @@ export class SpriteData { data: ImageSource, opt: LoadSpriteOpt = {}, ): SpriteData { - const [tex, quad] = assets.packer.add(data); + const [tex, quad, packerId] = assets.packer.add(data); const frames = opt.frames ? opt.frames.map((f) => new Quad( @@ -150,14 +153,15 @@ export class SpriteData { quad.w, quad.h, ); - return new SpriteData(tex, frames, opt.anims, opt.slice9); + + return new SpriteData(tex, frames, opt.anims, opt.slice9, packerId); } static fromURL( url: string, opt: LoadSpriteOpt = {}, ): Promise { - return loadImg(url).then((img) => SpriteData.fromImage(img, opt)); + return loadImage(url).then((img) => SpriteData.fromImage(img, opt)); } } @@ -179,9 +183,9 @@ export function resolveSprite( throw new Error(`Sprite not found: ${src}`); } } - else if (src instanceof SpriteData) { - return Asset.loaded(src); - } + // else if (src instanceof SpriteData) { + // return Asset.loaded(src); + // } else if (src instanceof Asset) { return src; } @@ -205,13 +209,14 @@ export function loadSprite( }, ): Asset { src = fixURL(src); + if (Array.isArray(src)) { if (src.some((s) => typeof s === "string")) { return assets.sprites.add( name, Promise.all(src.map((s) => { return typeof s === "string" - ? loadImg(s) + ? loadImage(s) : Promise.resolve(s); })).then((images) => createSpriteSheet(images, opt)), ); @@ -256,7 +261,6 @@ export function slice(x = 1, y = 1, dx = 0, dy = 0, w = 1, h = 1): Quad[] { } // TODO: load synchronously if passed ImageSource - export function createSpriteSheet( images: ImageSource[], opt: LoadSpriteOpt = {}, @@ -266,8 +270,10 @@ export function createSpriteSheet( const height = images[0].height; canvas.width = width * images.length; canvas.height = height; + const c2d = canvas.getContext("2d"); if (!c2d) throw new Error("Failed to create canvas context"); + images.forEach((img, i) => { if (img instanceof ImageData) { c2d.putImageData(img, i * width, 0); @@ -276,7 +282,9 @@ export function createSpriteSheet( c2d.drawImage(img, i * width, 0); } }); + const merged = c2d.getImageData(0, 0, images.length * width, height); + return SpriteData.fromImage(merged, { ...opt, sliceX: images.length, diff --git a/src/audio/play.ts b/src/audio/play.ts index 686e6d06..51712dbe 100644 --- a/src/audio/play.ts +++ b/src/audio/play.ts @@ -53,7 +53,7 @@ export interface AudioPlayOpt { * If the audio node should start out connected to another audio node rather than * KAPLAY's default volume node. Defaults to undefined, i.e. use KAPLAY's volume node. */ - connectTo?: AudioNode + connectTo?: AudioNode; } export interface AudioPlay { diff --git a/src/components/draw/particles.ts b/src/components/draw/particles.ts index 9b8d991b..0f3e65e3 100644 --- a/src/components/draw/particles.ts +++ b/src/components/draw/particles.ts @@ -2,6 +2,7 @@ import { dt } from "../../app"; import { drawRaw, type Texture } from "../../gfx"; import { Color, + deg2rad, lerp, map, Quad, @@ -14,6 +15,9 @@ import { import type { Comp, Vertex } from "../../types"; import { KEvent } from "../../utils/"; +/** + * A particle. Used on the {@link particles `particles()`} component. + */ class Particle { pos: Vec2 = vec2(0); vel: Vec2 = vec2(0); @@ -21,13 +25,11 @@ class Particle { angle: number = 0; angularVelocity: number = 0; damping: number = 0; - t: number; + t: number = 0; lt: number | null = null; - gc: boolean; + gc: boolean = true; constructor() { - this.t = 0; - this.gc = true; } get progress() { @@ -35,9 +37,12 @@ class Particle { } } +/** + * Options for the {@link particles `particles()`}'s component + */ export type EmitterOpt = { /* - * Shape of the emitter. If given, particles spwan within this shape. + * Shape of the emitter. If given, particles spawn within this shape. */ shape?: ShapeType; /* @@ -58,6 +63,11 @@ export type EmitterOpt = { spread: number; }; +/** + * Options for the {@link particles `particles()`}'s component + * + * @group Component Types + */ export type ParticlesOpt = { /* * Maximum number of simultaneously rendered particles. @@ -109,6 +119,11 @@ export type ParticlesOpt = { texture: Texture; }; +/** + * The {@link particles `particles()`} component. + * + * @group Component Types + */ export interface ParticlesComp extends Comp { /* * Emit a number of particles @@ -123,22 +138,27 @@ export interface ParticlesComp extends Comp { export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { let emitterLifetime = eopt.lifetime; - const particles: Particle[] = []; + const particles: Particle[] = new Array(popt.max); const colors = popt.colors || [Color.WHITE]; const opacities = popt.opacities || [1]; const quads = popt.quads || [new Quad(0, 0, 1, 1)]; const scales = popt.scales || [1]; const lifetime = popt.lifeTime; - const direction = eopt.direction; - const spread = eopt.spread; + const direction = eopt.direction || 0; + const spread = eopt.spread || 0; const speed = popt.speed || [0, 0]; const angleRange = popt.angle || [0, 0]; const angularVelocityRange = popt.angularVelocity || [0, 0]; const accelerationRange = popt.acceleration || [vec2(0), vec2(0)]; const dampingRange = popt.damping || [0, 0]; - const indices: number[] = []; - const vertices: Vertex[] = new Array(popt.max); + const indices: number[] = new Array(popt.max * 6); + const attributes = { + pos: new Array(popt.max * 4 * 2), + uv: new Array(popt.max * 4 * 2), + color: new Array(popt.max * 4 * 3), + opacity: new Array(popt.max * 4), + }; let count = 0; let time = 0; @@ -150,14 +170,11 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { indices[i * 6 + 4] = i * 4 + 2; indices[i * 6 + 5] = i * 4 + 3; - for (let j = 0; j < 4; j++) { - vertices[i * 4 + j] = { - pos: new Vec2(0, 0), - uv: new Vec2(0, 0), - color: rgb(255, 255, 255), - opacity: 1, - }; - } + attributes.pos.fill(0); + attributes.uv.fill(0); + attributes.color.fill(255); + attributes.opacity.fill(1); + particles[i] = new Particle(); } @@ -176,6 +193,7 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { return { id: "particles", emit(n: number) { + n = Math.min(n, popt.max - count); let index: number | null = 0; for (let i = 0; i < n; i++) { index = nextFree(index); @@ -207,6 +225,7 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { : vec2(); const p = particles[index]; + p.t = 0; p.lt = lt; p.pos = pos; p.vel = vel; @@ -214,7 +233,6 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { p.angle = angle; p.angularVelocity = angularVelocity; p.damping = damping; - p.angularVelocity = angularVelocity; p.gc = false; } count += n; @@ -223,14 +241,16 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { if (emitterLifetime !== undefined && emitterLifetime <= 0) { return; } + const DT = dt(); // Update all particles - for (const p of particles) { + for (let i = 0; i < particles.length; i++) { + const p = particles[i]; if (p.gc) { continue; } p.t += DT; - if (p.lt && p.t >= p.lt) { + if (p.lt !== null && p.t >= p.lt) { p.gc = true; count--; continue; @@ -253,12 +273,14 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { && time > eopt.rate ) { this.emit(1); - count++; time -= eopt.rate; } }, draw() { - if (emitterLifetime !== undefined && emitterLifetime <= 0) { + if ( + (emitterLifetime !== undefined && emitterLifetime <= 0) + || count == 0 + ) { return; } @@ -266,10 +288,14 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { for (let i = 0; i < particles.length; i++) { const p = particles[i]; if (p.gc) { + attributes.opacity[i * 4] = 0; + attributes.opacity[i * 4 + 1] = 0; + attributes.opacity[i * 4 + 2] = 0; + attributes.opacity[i * 4 + 3] = 0; continue; } const progress = p.progress; - const colorIndex = Math.floor(p.progress * colors.length); + const colorIndex = Math.floor(progress * colors.length); const color = colorIndex < colors.length - 1 ? lerp( colors[colorIndex], @@ -283,7 +309,7 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { ), ) : colors[colorIndex]; - const opacityIndex = Math.floor(p.progress * opacities.length); + const opacityIndex = Math.floor(progress * opacities.length); const opacity = opacityIndex < opacities.length - 1 ? lerp( opacities[opacityIndex], @@ -298,63 +324,70 @@ export function particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp { ) : opacities[opacityIndex]; - const quadIndex = Math.floor(p.progress * quads.length); + const quadIndex = Math.floor(progress * quads.length); const quad = quads[quadIndex]; - const scaleIndex = Math.floor(p.progress * scales.length); + const scaleIndex = Math.floor(progress * scales.length); const scale = scales[scaleIndex]; - const c = Math.cos(p.angle * Math.PI / 180); - const s = Math.sin(p.angle * Math.PI / 180); + // TODO: lerp scale + const angle = deg2rad(p.angle); + const c = Math.cos(angle); + const s = Math.sin(angle); - const hw = (popt.texture ? popt.texture.width : 10) * quad.w - / 2; - const hh = (popt.texture ? popt.texture.height : 10) * quad.h - / 2; + const hw = popt.texture.width * quad.w / 2; + const hh = popt.texture.height * quad.h / 2; let j = i * 4; // Left top - let v = vertices[j]; - v.pos.x = p.pos.x + (-hw) * scale * c - (-hh) * scale * s; - v.pos.y = p.pos.y + (-hw) * scale * s + (-hh) * scale * c; - v.uv.x = quad.x; - v.uv.y = quad.y; - v.color.r = color.r; - v.color.g = color.g; - v.color.b = color.b; - v.opacity = opacity; + attributes.pos[j * 2] = p.pos.x + (-hw) * scale * c + - (-hh) * scale * s; + attributes.pos[j * 2 + 1] = p.pos.y + (-hw) * scale * s + + (-hh) * scale * c; + attributes.uv[j * 2] = quad.x; + attributes.uv[j * 2 + 1] = quad.y; + attributes.color[j * 3] = color.r; + attributes.color[j * 3 + 1] = color.g; + attributes.color[j * 3 + 2] = color.b; + attributes.opacity[j] = opacity; // Right top - v = vertices[j + 1]; - v.pos.x = p.pos.x + hw * scale * c - (-hh) * scale * s; - v.pos.y = p.pos.y + hw * scale * s + (-hh) * scale * c; - v.uv.x = quad.x + quad.w; - v.uv.y = quad.y; - v.color.r = color.r; - v.color.g = color.g; - v.color.b = color.b; - v.opacity = opacity; + j++; + attributes.pos[j * 2] = p.pos.x + hw * scale * c + - (-hh) * scale * s; + attributes.pos[j * 2 + 1] = p.pos.y + hw * scale * s + + (-hh) * scale * c; + attributes.uv[j * 2] = quad.x + quad.w; + attributes.uv[j * 2 + 1] = quad.y; + attributes.color[j * 3] = color.r; + attributes.color[j * 3 + 1] = color.g; + attributes.color[j * 3 + 2] = color.b; + attributes.opacity[j] = opacity; // Right bottom - v = vertices[j + 2]; - v.pos.x = p.pos.x + hw * scale * c - hh * scale * s; - v.pos.y = p.pos.y + hw * scale * s + hh * scale * c; - v.uv.x = quad.x + quad.w; - v.uv.y = quad.y + quad.h; - v.color.r = color.r; - v.color.g = color.g; - v.color.b = color.b; - v.opacity = opacity; + j++; + attributes.pos[j * 2] = p.pos.x + hw * scale * c + - hh * scale * s; + attributes.pos[j * 2 + 1] = p.pos.y + hw * scale * s + + hh * scale * c; + attributes.uv[j * 2] = quad.x + quad.w; + attributes.uv[j * 2 + 1] = quad.y + quad.h; + attributes.color[j * 3] = color.r; + attributes.color[j * 3 + 1] = color.g; + attributes.color[j * 3 + 2] = color.b; + attributes.opacity[j] = opacity; // Left bottom - v = vertices[j + 3]; - v.pos.x = p.pos.x + (-hw) * scale * c - hh * scale * s; - v.pos.y = p.pos.y + (-hw) * scale * s + hh * scale * c; - v.uv.x = quad.x; - v.uv.y = quad.y + quad.h; - v.color.r = color.r; - v.color.g = color.g; - v.color.b = color.b; - v.opacity = opacity; + j++; + attributes.pos[j * 2] = p.pos.x + (-hw) * scale * c + - hh * scale * s; + attributes.pos[j * 2 + 1] = p.pos.y + (-hw) * scale * s + + hh * scale * c; + attributes.uv[j * 2] = quad.x; + attributes.uv[j * 2 + 1] = quad.y + quad.h; + attributes.color[j * 3] = color.r; + attributes.color[j * 3 + 1] = color.g; + attributes.color[j * 3 + 2] = color.b; + attributes.opacity[j] = opacity; } drawRaw( - vertices, + attributes, indices, (this as any).fixed, popt.texture, diff --git a/src/components/draw/sprite.ts b/src/components/draw/sprite.ts index d6c3eba1..e3265beb 100644 --- a/src/components/draw/sprite.ts +++ b/src/components/draw/sprite.ts @@ -35,9 +35,13 @@ export interface SpriteComp extends Comp { */ height: number; /** - * Current frame. + * Current frame in the entire spritesheet. */ frame: number; + /** + * Current frame in relative to the animation that is currently playing. + */ + animFrame: number; /** * The rectangular area of the texture to render. */ @@ -243,6 +247,20 @@ export function sprite( } }, + get animFrame() { + if (!spriteData || !curAnim || curAnimDir === null) { + return this.frame; + } + + const anim = spriteData.anims[curAnim.name]; + + if (typeof anim === "number") { + return anim; + } + + return this.frame - Math.min(anim.from, anim.to); + }, + draw(this: GameObj) { if (!spriteData) return; diff --git a/src/components/level/index.ts b/src/components/level/index.ts index e6cbbb7b..b49f382b 100644 --- a/src/components/level/index.ts +++ b/src/components/level/index.ts @@ -1,5 +1,5 @@ export * from "./agent"; -export * from "./navigation"; +export * from "./pathfinder"; export * from "./patrol"; export * from "./sentry"; export * from "./tile"; diff --git a/src/components/level/navigation.ts b/src/components/level/pathfinder.ts similarity index 75% rename from src/components/level/navigation.ts rename to src/components/level/pathfinder.ts index e0741167..7f8bbdde 100644 --- a/src/components/level/navigation.ts +++ b/src/components/level/pathfinder.ts @@ -3,7 +3,7 @@ import { type Graph } from "../../math/navigation"; import type { Comp, GameObj } from "../../types"; import type { PosComp } from "../transform/pos"; -export interface NavigationMapComp extends Comp { +export interface PathfinderMapComp extends Comp { /* * Get navigation waypoints to reach the given target from the given origin. */ @@ -18,19 +18,19 @@ export interface NavigationMapComp extends Comp { graph: Graph | undefined; } -export interface NavigationMapCompOpt { +export interface PathfinderMapCompOpt { /* - * The graph to use for navigation. If null, the ancestors are queried for a navigatorMap component. + * The graph to use for navigation. If null, the ancestors are queried for a pathfinderMap component. */ graph?: Graph; } -export function navigationMap( - opts: NavigationMapCompOpt, -): NavigationMapComp { +export function pathfinderMap( + opts: PathfinderMapCompOpt, +): PathfinderMapComp { let graph = opts.graph; return { - id: "navigatorMap", + id: "pathfinderMap", get graph(): Graph | undefined { return graph; }, @@ -38,7 +38,7 @@ export function navigationMap( graph = value; }, navigate( - this: GameObj, + this: GameObj, origin: Vec2, target: Vec2, navigationOpt: any = {}, @@ -48,7 +48,7 @@ export function navigationMap( }; } -export interface NavigationComp extends Comp { +export interface PathfinderComp extends Comp { /* * Get navigation waypoints to reach the given target from the current position. */ @@ -59,9 +59,9 @@ export interface NavigationComp extends Comp { graph: Graph | undefined; } -export interface NavigationCompOpt { +export interface PathfinderCompOpt { /* - * The graph to use for navigation. If null, the ancestors are queried for a navigatorMap component. + * The graph to use for navigation. If null, the ancestors are queried for a pathfinderMap component. */ graph?: Graph; /* @@ -70,15 +70,15 @@ export interface NavigationCompOpt { navigationOpt?: any; } -export function navigation( - opts: NavigationCompOpt, -): NavigationComp { +export function pathfinder( + opts: PathfinderCompOpt, +): PathfinderComp { let graph = opts.graph; return { - id: "navigator", + id: "pathfinder", require: ["pos"], navigateTo( - this: GameObj, + this: GameObj, target: Vec2, ): Vec2[] | undefined { const graph: Graph | undefined = this.graph; @@ -89,9 +89,9 @@ export function navigation( return graph; } let parent: GameObj | null = - (this as unknown as GameObj).parent; + (this as unknown as GameObj).parent; while (parent) { - if (parent.is("navigatormap")) { + if (parent.is("pathfinderMap")) { return parent.graph; } parent = parent.parent; diff --git a/src/components/physics/area.ts b/src/components/physics/area.ts index 8405086d..ee53a891 100644 --- a/src/components/physics/area.ts +++ b/src/components/physics/area.ts @@ -539,15 +539,15 @@ export function area(opt: AreaCompOpt = {}): AreaComp { const transform = this.transform .clone() - .translate(this.area.offset) - .scale(vec2(this.area.scale ?? 1)); + .translateSelfV(this.area.offset) + .scaleSelfV(vec2(this.area.scale ?? 1)); if (localArea instanceof k.Rect) { const offset = anchorPt(this.anchor || DEF_ANCHOR) .add(1, 1) .scale(-0.5) .scale(localArea.width, localArea.height); - transform.translate(offset); + transform.translateSelfV(offset); } return localArea.transform(transform) as Polygon; diff --git a/src/components/physics/body.ts b/src/components/physics/body.ts index 5a47d857..39b2308a 100644 --- a/src/components/physics/body.ts +++ b/src/components/physics/body.ts @@ -236,8 +236,8 @@ export function body(opt: BodyCompOpt = {}): BodyComp { other.pos = other.pos.add( col.displacement.scale(-this.mass / tmass), ); - this.transform = calcTransform(this); - other.transform = calcTransform(other); + calcTransform(this, this.transform); + calcTransform(other, other.transform); } else { // if one is static and on is not, resolve the non static one @@ -247,9 +247,7 @@ export function body(opt: BodyCompOpt = {}): BodyComp { col2.source.pos = col2.source.pos.add( col2.displacement, ); - col2.source.transform = calcTransform( - col2.source, - ); + calcTransform(col2.source, col2.source.transform); } col.resolved = true; @@ -302,15 +300,8 @@ export function body(opt: BodyCompOpt = {}): BodyComp { // Clear the velocity in the direction of the normal, as we've hit something if (this.vel.dot(col.normal) < 0) { - if (restitution == 0) { - this.vel = rejection; - } - else { - // Modulate the velocity tangential to the normal - this.vel = this.vel.reflect(col.normal).scale( - restitution, - ); - } + // Modulate the velocity tangential to the normal + this.vel = rejection.sub(projection.scale(restitution)); } if (friction != 0) { diff --git a/src/components/physics/effectors.ts b/src/components/physics/effectors.ts index e4b3bed1..11aecdba 100644 --- a/src/components/physics/effectors.ts +++ b/src/components/physics/effectors.ts @@ -39,37 +39,30 @@ export function surfaceEffector( export type AreaEffectorCompOpt = { useGlobalAngle?: boolean; - forceAngle: number; - forceMagnitude: number; - forceVariation?: number; + force: Vec2; linearDrag?: number; - // angularDrag?: number; }; export interface AreaEffectorComp extends Comp { useGlobalAngle: boolean; - forceAngle: number; - forceMagnitude: number; - forceVariation: number; - linearDrag?: number; - // angularDrag?: number; + force: Vec2; + linearDrag: number; } export function areaEffector(opts: AreaEffectorCompOpt): AreaEffectorComp { return { id: "areaEffector", require: ["area"], - useGlobalAngle: opts.useGlobalAngle || false, - forceAngle: opts.forceAngle, - forceMagnitude: opts.forceMagnitude, - forceVariation: opts.forceVariation ?? 0, + force: opts.force, linearDrag: opts.linearDrag ?? 0, - // angularDrag: opts.angularDrag ?? 0, + useGlobalAngle: opts.useGlobalAngle ?? true, add(this: GameObj) { - this.onCollideUpdate("body", (obj, col) => { - const dir = Vec2.fromAngle(this.forceAngle); - const force = dir.scale(this.forceMagnitude); - obj.addForce(force); + this.onCollideUpdate("body", obj => { + obj.addForce( + this.useGlobalAngle + ? this.force + : this.force.rotate(this.transform.getRotation()), + ); if (this.linearDrag) { obj.addForce(obj.vel.scale(-this.linearDrag)); } @@ -82,20 +75,16 @@ export type ForceMode = "constant" | "inverseLinear" | "inverseSquared"; export type PointEffectorCompOpt = { forceMagnitude: number; - forceVariation: number; distanceScale?: number; forceMode?: ForceMode; linearDrag?: number; - // angularDrag?: number; }; export interface PointEffectorComp extends Comp { forceMagnitude: number; - forceVariation: number; distanceScale: number; forceMode: ForceMode; linearDrag: number; - // angularDrag: number; } export function pointEffector(opts: PointEffectorCompOpt): PointEffectorComp { @@ -103,7 +92,6 @@ export function pointEffector(opts: PointEffectorCompOpt): PointEffectorComp { id: "pointEffector", require: ["area", "pos"], forceMagnitude: opts.forceMagnitude, - forceVariation: opts.forceVariation ?? 0, distanceScale: opts.distanceScale ?? 1, forceMode: opts.forceMode || "inverseLinear", linearDrag: opts.linearDrag ?? 0, @@ -116,8 +104,8 @@ export function pointEffector(opts: PointEffectorCompOpt): PointEffectorComp { const forceScale = this.forceMode === "constant" ? 1 : this.forceMode === "inverseLinear" - ? 1 / distance - : 1 / distance ** 2; + ? 1 / distance + : 1 / distance ** 2; const force = dir.scale( this.forceMagnitude * forceScale / length, ); @@ -132,10 +120,12 @@ export function pointEffector(opts: PointEffectorCompOpt): PointEffectorComp { export type ConstantForceCompOpt = { force?: Vec2; + useGlobalAngle?: boolean; }; export interface ConstantForceComp extends Comp { - force?: Vec2; + force: Vec2 | undefined; + useGlobalAngle: boolean; } export function constantForce(opts: ConstantForceCompOpt): ConstantForceComp { @@ -143,9 +133,14 @@ export function constantForce(opts: ConstantForceCompOpt): ConstantForceComp { id: "constantForce", require: ["body"], force: opts.force, + useGlobalAngle: opts.useGlobalAngle ?? true, update(this: GameObj) { if (this.force) { - this.addForce(this.force); + this.addForce( + this.useGlobalAngle + ? this.force + : this.force.rotate(this.transform.getRotation()), + ); } }, }; @@ -155,23 +150,27 @@ export type PlatformEffectorCompOpt = { /** * If the object is about to collide and the collision normal direction is * in here, the object won't collide. - * + * * Should be a list of unit vectors `LEFT`, `RIGHT`, `UP`, or `DOWN`. */ - ignoreSides?: Vec2[] + ignoreSides?: Vec2[]; /** * A function that determines whether the object should collide. - * + * * If present, it overrides the `ignoreSides`; if absent, it is * automatically created from `ignoreSides`. */ - shouldCollide?: (obj: GameObj, normal: Vec2) => boolean + shouldCollide?: ( + this: GameObj, + obj: GameObj, + normal: Vec2, + ) => boolean; }; export interface PlatformEffectorComp extends Comp { /** * A set of the objects that should not collide with this, because `shouldCollide` returned true. - * + * * Objects in here are automatically removed when they stop colliding, so the casual user shouldn't * need to touch this much. However, if an object is added to this set before the object collides * with the platform effector, it won't collide even if `shouldCollide` returns true. @@ -184,8 +183,9 @@ export function platformEffector( ): PlatformEffectorComp { opt.ignoreSides ??= [Vec2.UP]; opt.shouldCollide ??= (_, normal) => { - return opt.ignoreSides?.findIndex(s => s.sdist(normal) < Number.EPSILON) == -1; - } + return opt.ignoreSides?.findIndex(s => s.sdist(normal) < Number.EPSILON) + == -1; + }; return { id: "platformEffector", require: ["area", "body"], @@ -195,7 +195,13 @@ export function platformEffector( if (this.platformIgnore.has(collision.target)) { collision.preventResolution(); } - else if (!opt.shouldCollide!(collision.target, collision.normal)) { + else if ( + !opt.shouldCollide!.call( + this, + collision.target, + collision.normal, + ) + ) { collision.preventResolution(); this.platformIgnore.add(collision.target); } diff --git a/src/components/transform/pos.ts b/src/components/transform/pos.ts index f9c9766a..2880d011 100644 --- a/src/components/transform/pos.ts +++ b/src/components/transform/pos.ts @@ -115,7 +115,7 @@ export function pos(...args: Vec2Args): PosComp { } else { return this.parent - ? this.parent.transform.multVec2(this.pos) + ? this.parent.transform.transformPoint(this.pos, vec2()) : this.pos; } }, @@ -123,14 +123,16 @@ export function pos(...args: Vec2Args): PosComp { // Transform a local point to a world point toWorld(this: GameObj, p: Vec2): Vec2 { return this.parent - ? this.parent.transform.multVec2(this.pos.add(p)) + ? this.parent.transform.transformPoint(this.pos.add(p), vec2()) : this.pos.add(p); }, // Transform a world point (relative to the root) to a local point (relative to this) fromWorld(this: GameObj, p: Vec2): Vec2 { return this.parent - ? this.parent.transform.invert().multVec2(p).sub(this.pos) + ? this.parent.transform.inverse.transformPoint(p, vec2()).sub( + this.pos, + ) : p.sub(this.pos); }, diff --git a/src/constants.ts b/src/constants.ts index 782ce3f5..eea279d0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -96,7 +96,6 @@ export const COMP_EVENTS = new Set([ "inspect", "drawInspect", ]); -export const MULTI_WORD_RE = /^\w+$/; export const DEF_OFFSCREEN_DIS = 200; // maximum y velocity with body() export const DEF_JUMP_FORCE = 640; diff --git a/src/game/camera.ts b/src/game/camera.ts index 4ad5f217..6d0a27da 100644 --- a/src/game/camera.ts +++ b/src/game/camera.ts @@ -2,7 +2,7 @@ import { color, fixed, opacity, rect } from "../components"; import { center, getViewportScale, height, width } from "../gfx"; import { game } from "../kaplay"; import { type Color, rgb } from "../math/color"; -import { type Mat4, type Vec2, vec2, type Vec2Args } from "../math/math"; +import { type Mat23, type Vec2, vec2, type Vec2Args } from "../math/math"; import { destroy } from "."; export function camPos(...pos: Vec2Args): Vec2 { @@ -41,7 +41,7 @@ export function camFlash( return fade; } -export function camTransform(): Mat4 { +export function camTransform(): Mat23 { return game.cam.transform.clone(); } diff --git a/src/game/events/events.ts b/src/game/events/events.ts index 22c8f092..2f543432 100644 --- a/src/game/events/events.ts +++ b/src/game/events/events.ts @@ -187,22 +187,22 @@ export function onHoverEnd( } export function onLoading(action: (progress: number) => void) { - game.events.on("loading", action); + return game.events.on("loading", action); } export function onResize(action: () => void) { - app.onResize(action); + return app.onResize(action); } export function onError(action: (err: Error) => void) { - game.events.on("error", action); + return game.events.on("error", action); } -export function onLoad(cb: () => void): void { +export function onLoad(cb: () => void) { if (assets.loaded) { cb(); } else { - game.events.on("load", cb); + return game.events.on("load", cb); } } diff --git a/src/game/game.ts b/src/game/game.ts index 58a414d3..44f996fc 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -1,5 +1,5 @@ import type { TimerComp } from "../components"; -import { Mat4, Vec2 } from "../math/math"; +import { Mat23, Vec2 } from "../math/math"; import { type GameObj, type Key, type MouseButton } from "../types"; import { KEventHandler } from "../utils"; import type { GameObjEventMap } from "./events"; @@ -65,7 +65,7 @@ export const initGame = () => { scale: new Vec2(1), angle: 0, shake: 0, - transform: new Mat4(), + transform: new Mat23(), }, }; diff --git a/src/game/level.ts b/src/game/level.ts index 54020c56..72e4580e 100644 --- a/src/game/level.ts +++ b/src/game/level.ts @@ -353,7 +353,7 @@ export function addLevel( obj.tilePos = p; // Stale, so recalculate - obj.transform = calcTransform(obj); + calcTransform(obj, obj.transform); if (spatialMap) { insertIntoSpatialMap(obj); @@ -424,10 +424,8 @@ export function addLevel( const worldDirection = this.toWorld(origin.add(direction)).sub( worldOrigin, ); - const levelOrigin = origin.scale( - 1 / this.tileWidth(), - 1 / this.tileHeight(), - ); + const invTileWidth = 1 / this.tileWidth(); + const levelOrigin = origin.scale(invTileWidth); const hit = raycastGrid(levelOrigin, direction, (tilePos: Vec2) => { const tiles = this.getAt(tilePos); if (tiles.some(t => t.isObstacle)) { @@ -455,13 +453,15 @@ export function addLevel( } } } - return minHit! || false; + if (minHit) { + minHit.point = this.fromWorld(minHit.point).scale( + invTileWidth, + ); + } + return minHit || false; }, 64); if (hit) { - hit.point = hit.point.scale( - this.tileWidth(), - this.tileHeight(), - ); + hit.point = hit.point.scale(this.tileWidth()); } return hit; }, @@ -590,7 +590,7 @@ export function addLevel( while (node !== start) { let cameNode = cameFrom.get(node); - if (!cameNode) { + if (cameNode === undefined) { throw new Error("Bug in pathfinding algorithm"); } diff --git a/src/game/make.ts b/src/game/make.ts index 762be681..cf3dec57 100644 --- a/src/game/make.ts +++ b/src/game/make.ts @@ -11,12 +11,12 @@ import { flush, popTransform, pushRotate, - pushScale, + pushScaleV, pushTransform, - pushTranslate, + pushTranslateV, } from "../gfx"; import { app, game, gfx, k } from "../kaplay"; -import { Mat4 } from "../math/math"; +import { Mat23 } from "../math/math"; import { calcTransform } from "../math/various"; import { type Comp, @@ -27,7 +27,7 @@ import { type QueryOpt, type Tag, } from "../types"; -import { type KEventController, KEventHandler, uid } from "../utils"; +import { KEventController, KEventHandler, uid } from "../utils"; export function make(comps: CompList = []): GameObj { const compStates = new Map(); @@ -43,7 +43,7 @@ export function make(comps: CompList = []): GameObj { id: uid(), // TODO: a nice way to hide / pause when add()-ing hidden: false, - transform: new Mat4(), + transform: new Mat23(), children: [], parent: null, @@ -77,7 +77,7 @@ export function make(comps: CompList = []): GameObj { ); } obj.parent = this; - obj.transform = calcTransform(obj); + calcTransform(obj, obj.transform); this.children.push(obj); // TODO: trigger add for children obj.trigger("add", obj); @@ -150,8 +150,8 @@ export function make(comps: CompList = []): GameObj { const f = gfx.fixed; if (this.fixed) gfx.fixed = true; pushTransform(); - pushTranslate(this.pos); - pushScale(this.scale); + pushTranslateV(this.pos); + pushScaleV(this.scale); pushRotate(this.angle); const children = this.children.sort((o1, o2) => { const l1 = o1.layerIndex ?? game.defaultLayerIndex; @@ -168,14 +168,18 @@ export function make(comps: CompList = []): GameObj { throw new Error(`Invalid mask func: "${this.mask}"`); } maskFunc(() => { - children.forEach((child) => child.draw()); + for (let i = 0; i < children.length; i++) { + children[i].draw(); + } }, () => { this.trigger("draw"); }); } else { this.trigger("draw"); - children.forEach((child) => child.draw()); + for (let i = 0; i < children.length; i++) { + children[i].draw(); + } } popTransform(); gfx.fixed = f; @@ -188,8 +192,8 @@ export function make(comps: CompList = []): GameObj { drawInspect(this: GameObj) { if (this.hidden) return; pushTransform(); - pushTranslate(this.pos); - pushScale(this.scale); + pushTranslateV(this.pos); + pushScaleV(this.scale); pushRotate(this.angle); this.children /*.sort((o1, o2) => (o1.z ?? 0) - (o2.z ?? 0))*/ @@ -601,10 +605,18 @@ export function make(comps: CompList = []): GameObj { obj.onDestroy(() => ev.cancel()); obj.on("sceneEnter", () => { - ev.cancel(); + // All app events are already canceled by changing the scene + // not neccesary -> ev.cancel(); inputEvents.splice(inputEvents.indexOf(ev), 1); - app[e]?.(...args); + // create a new event with the same arguments + const newEv = app[e]?.(...args); + + // Replace the old event handler with the new one + // old KEventController.cancel() => new KEventController.cancel() + KEventController.replace(ev, newEv); + inputEvents.push(ev); }); + return ev; }; } diff --git a/src/game/scenes.ts b/src/game/scenes.ts index 70768607..1559eed7 100644 --- a/src/game/scenes.ts +++ b/src/game/scenes.ts @@ -1,5 +1,5 @@ import { app, game } from "../kaplay"; -import { Mat4, vec2 } from "../math/math"; +import { Mat23, vec2 } from "../math/math"; import type { KEventController } from "../utils"; import { initEvents } from "./initEvents"; @@ -45,7 +45,7 @@ export function go(name: SceneName, ...args: unknown[]) { scale: vec2(1), angle: 0, shake: 0, - transform: new Mat4(), + transform: new Mat23(), }; game.scenes[name](...args); diff --git a/src/gfx/anchor.ts b/src/gfx/anchor.ts index a08b758e..2abbe49b 100644 --- a/src/gfx/anchor.ts +++ b/src/gfx/anchor.ts @@ -2,27 +2,37 @@ import { Vec2 } from "../math/math"; import { type Anchor } from "../types"; import type { TextAlign } from "./draw"; +const TOP_LEFT = new Vec2(-1, -1); +const TOP = new Vec2(0, -1); +const TOP_RIGHT = new Vec2(1, -1); +const LEFT = new Vec2(-1, 0); +const CENTER = new Vec2(0, 0); +const RIGHT = new Vec2(1, 0); +const BOTTOM_LEFT = new Vec2(-1, 1); +const BOTTOM = new Vec2(0, 1); +const BOTTOM_RIGHT = new Vec2(1, 1); + // convert anchor string to a vec2 offset export function anchorPt(orig: Anchor | Vec2): Vec2 { switch (orig) { case "topleft": - return new Vec2(-1, -1); + return TOP_LEFT; case "top": - return new Vec2(0, -1); + return TOP; case "topright": - return new Vec2(1, -1); + return TOP_RIGHT; case "left": - return new Vec2(-1, 0); + return LEFT; case "center": - return new Vec2(0, 0); + return CENTER; case "right": - return new Vec2(1, 0); + return RIGHT; case "botleft": - return new Vec2(-1, 1); + return BOTTOM_LEFT; case "bot": - return new Vec2(0, 1); + return BOTTOM; case "botright": - return new Vec2(1, 1); + return BOTTOM_RIGHT; default: return orig; } diff --git a/src/gfx/classes/FrameBuffer.ts b/src/gfx/classes/FrameBuffer.ts new file mode 100644 index 00000000..4c43b8fe --- /dev/null +++ b/src/gfx/classes/FrameBuffer.ts @@ -0,0 +1,128 @@ +import type { TextureOpt } from "../../types"; +import { type GfxCtx, Texture } from "../gfx"; + +/** + * @group GFX + */ + +export class FrameBuffer { + ctx: GfxCtx; + tex: Texture; + glFramebuffer: WebGLFramebuffer; + glRenderbuffer: WebGLRenderbuffer; + + constructor(ctx: GfxCtx, w: number, h: number, opt: TextureOpt = {}) { + this.ctx = ctx; + const gl = ctx.gl; + ctx.onDestroy(() => this.free()); + this.tex = new Texture(ctx, w, h, opt); + + const frameBuffer = gl.createFramebuffer(); + const renderBuffer = gl.createRenderbuffer(); + + if (!frameBuffer || !renderBuffer) { + throw new Error("Failed to create framebuffer"); + } + + this.glFramebuffer = frameBuffer; + this.glRenderbuffer = renderBuffer; + + this.bind(); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.tex.glTex, + 0, + ); + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_STENCIL_ATTACHMENT, + gl.RENDERBUFFER, + this.glRenderbuffer, + ); + this.unbind(); + } + + get width() { + return this.tex.width; + } + + get height() { + return this.tex.height; + } + + toImageData() { + const gl = this.ctx.gl; + const data = new Uint8ClampedArray(this.width * this.height * 4); + this.bind(); + gl.readPixels( + 0, + 0, + this.width, + this.height, + gl.RGBA, + gl.UNSIGNED_BYTE, + data, + ); + this.unbind(); + // flip vertically + const bytesPerRow = this.width * 4; + const temp = new Uint8Array(bytesPerRow); + for (let y = 0; y < (this.height / 2 | 0); y++) { + const topOffset = y * bytesPerRow; + const bottomOffset = (this.height - y - 1) * bytesPerRow; + temp.set(data.subarray(topOffset, topOffset + bytesPerRow)); + data.copyWithin( + topOffset, + bottomOffset, + bottomOffset + bytesPerRow, + ); + data.set(temp, bottomOffset); + } + return new ImageData(data, this.width, this.height); + } + + toDataURL() { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = this.width; + canvas.height = this.height; + + if (!ctx) throw new Error("Failed to get 2d context"); + + ctx.putImageData(this.toImageData(), 0, 0); + return canvas.toDataURL(); + } + + clear() { + const gl = this.ctx.gl; + gl.clear(gl.COLOR_BUFFER_BIT); + } + + draw(action: () => void) { + this.bind(); + action(); + this.unbind(); + } + + bind() { + this.ctx.pushFramebuffer(this.glFramebuffer); + this.ctx.pushRenderbuffer(this.glRenderbuffer); + this.ctx.pushViewport({ x: 0, y: 0, w: this.width, h: this.height }); + } + + unbind() { + this.ctx.popFramebuffer(); + this.ctx.popRenderbuffer(); + this.ctx.popViewport(); + } + + free() { + const gl = this.ctx.gl; + gl.deleteFramebuffer(this.glFramebuffer); + gl.deleteRenderbuffer(this.glRenderbuffer); + this.tex.free(); + } +} diff --git a/src/gfx/texPacker.ts b/src/gfx/classes/TexPacker.ts similarity index 65% rename from src/gfx/texPacker.ts rename to src/gfx/classes/TexPacker.ts index 1ac1d98a..6af9ba49 100644 --- a/src/gfx/texPacker.ts +++ b/src/gfx/classes/TexPacker.ts @@ -1,18 +1,23 @@ -import type { ImageSource } from "../types"; - -import { type GfxCtx, Texture } from "../gfx"; - -import { Quad, Vec2 } from "../math/math"; +import { Quad, Vec2 } from "../../math/math"; +import type { ImageSource } from "../../types"; +import { type GfxCtx, Texture } from ".."; export default class TexPacker { + private lastTextureId: number = 0; private textures: Texture[] = []; private bigTextures: Texture[] = []; + private texturesPosition: Map = new Map(); private canvas: HTMLCanvasElement; private c2d: CanvasRenderingContext2D; private x: number = 0; private y: number = 0; private curHeight: number = 0; private gfx: GfxCtx; + constructor(gfx: GfxCtx, w: number, h: number) { this.gfx = gfx; this.canvas = document.createElement("canvas"); @@ -26,18 +31,21 @@ export default class TexPacker { this.c2d = context2D; } - add(img: ImageSource): [Texture, Quad] { + + add(img: ImageSource): [Texture, Quad, number] { if (img.width > this.canvas.width || img.height > this.canvas.height) { const tex = Texture.fromImage(this.gfx, img); this.bigTextures.push(tex); - return [tex, new Quad(0, 0, 1, 1)]; + return [tex, new Quad(0, 0, 1, 1), 0]; } + // next row if (this.x + img.width > this.canvas.width) { this.x = 0; this.y += this.curHeight; this.curHeight = 0; } + // next texture if (this.y + img.height > this.canvas.height) { this.c2d.clearRect(0, 0, this.canvas.width, this.canvas.height); @@ -46,19 +54,35 @@ export default class TexPacker { this.y = 0; this.curHeight = 0; } + const curTex = this.textures[this.textures.length - 1]; const pos = new Vec2(this.x, this.y); + const differenceWidth = this.canvas.width - this.x; + const differenceHeight = this.canvas.height - this.y; + this.x += img.width; + if (img.height > this.curHeight) { this.curHeight = img.height; } + if (img instanceof ImageData) { this.c2d.putImageData(img, pos.x, pos.y); } else { this.c2d.drawImage(img, pos.x, pos.y); } + curTex.update(this.canvas); + + this.texturesPosition.set(this.lastTextureId, { + position: pos, + size: new Vec2(img.width, img.height), + texture: curTex, + }); + + this.lastTextureId++; + return [ curTex, new Quad( @@ -67,6 +91,7 @@ export default class TexPacker { img.width / this.canvas.width, img.height / this.canvas.height, ), + this.lastTextureId - 1, ]; } free() { @@ -77,4 +102,22 @@ export default class TexPacker { tex.free(); } } + remove(packerId: number) { + const tex = this.texturesPosition.get(packerId); + + if (!tex) { + throw new Error("Texture with packer id not found"); + } + + this.c2d.clearRect( + tex.position.x, + tex.position.y, + tex.size.x, + tex.size.y, + ); + + tex.texture.update(this.canvas); + this.texturesPosition.delete(packerId); + this.x -= tex.size.x; + } } diff --git a/src/gfx/draw/drawDebug.ts b/src/gfx/draw/drawDebug.ts index c67d59c9..1b16bf66 100644 --- a/src/gfx/draw/drawDebug.ts +++ b/src/gfx/draw/drawDebug.ts @@ -178,9 +178,7 @@ export function drawDebug() { const style = log.msg instanceof Error ? "error" : "info"; str += `[time]${log.time.toFixed(2)}[/time]`; str += " "; - str += `[${style}]${ - typeof log?.msg === "string" ? log.msg : String(log.msg) - }[/${style}]`; + str += `[${style}]${prettyDebug(log.msg)}[/${style}]`; logs.push(str); } @@ -220,3 +218,41 @@ export function drawDebug() { }); } } + +function prettyDebug(object: any | undefined, inside: boolean = false): string { + var outStr = "", tmp; + if (inside && typeof object === "string") { + object = JSON.stringify(object); + } + if (Array.isArray(object)) { + outStr = [ + "[", + object.map(e => prettyDebug(e, true)).join(", "), + "]", + ].join(""); + object = outStr; + } + if ( + typeof object === "object" + && object.toString === Object.prototype.toString + ) { + if (object.constructor !== Object) { + outStr += object.constructor.name + " "; + } + outStr += [ + "{", + (tmp = Object.getOwnPropertyNames(object) + .map(p => + `${/^\w+$/.test(p) ? p : JSON.stringify(p)}: ${ + prettyDebug(object[p], true) + }` + ) + .join(", ")) + ? ` ${tmp} ` + : "", + "}", + ].join(""); + object = outStr; + } + return String(object).replaceAll(/(?= width()) { - pushTranslate(vec2(-bw, 0)); + pushTranslateV(vec2(-bw, 0)); } if (pos.y + bh >= height()) { - pushTranslate(vec2(0, -bh)); + pushTranslateV(vec2(0, -bh)); } drawRect({ diff --git a/src/gfx/draw/drawLine.ts b/src/gfx/draw/drawLine.ts index 6ac72d7a..0da0496b 100644 --- a/src/gfx/draw/drawLine.ts +++ b/src/gfx/draw/drawLine.ts @@ -1,3 +1,4 @@ +import { opacity } from "../../components"; import { gfx } from "../../kaplay"; import { Color } from "../../math/color"; import { deg2rad, Vec2, vec2 } from "../../math/math"; @@ -38,7 +39,7 @@ export function drawLine(opt: DrawLineOpt) { const dis = p2.sub(p1).unit().normal().scale(w * 0.5); // calculate the 4 corner points of the line polygon - const verts = [ + /*const verts = [ p1.sub(dis), p1.add(dis), p2.add(dis), @@ -48,10 +49,56 @@ export function drawLine(opt: DrawLineOpt) { uv: new Vec2(0), color: opt.color ?? Color.WHITE, opacity: opt.opacity ?? 1, - })); + }));*/ + + const color = opt.color ?? Color.WHITE; + const opacity = opt.opacity ?? 1; + + const attributes = { + pos: [ + p1.x - dis.x, + p1.y - dis.y, + p1.x + dis.x, + p1.y + dis.y, + p2.x + dis.x, + p2.y + dis.y, + p2.x - dis.x, + p2.y - dis.y, + ], + uv: [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + color: [ + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + ], + opacity: [ + opacity, + opacity, + opacity, + opacity, + ], + }; drawRaw( - verts, + attributes, [0, 1, 3, 1, 2, 3], opt.fixed, gfx.defTex, @@ -229,13 +276,6 @@ export function _drawLinesBevel(opt: DrawLinesOpt) { if (vertices.length < 4) return; - const verts = vertices.map(v => ({ - pos: offset.add(v), - uv: vec2(), - color: opt.color || Color.WHITE, - opacity: opt.opacity ?? 1, - })); - const indices = []; let index = 0; for (let i = 0; i < vertices.length - 2; i += 2) { @@ -256,8 +296,32 @@ export function _drawLinesBevel(opt: DrawLinesOpt) { indices[index++] = vertices.length - 1; } + /*const verts = vertices.map(v => ({ + pos: offset.add(v), + uv: vec2(), + color: opt.color || Color.WHITE, + opacity: opt.opacity ?? 1, + }));*/ + + const attributes = { + pos: new Array(vertices.length * 2), + uv: new Array(vertices.length * 2).fill(0), + color: new Array(vertices.length * 3).fill(255), + opacity: new Array(vertices.length).fill(opt.opacity ?? 1), + }; + + for (let i = 0; i < vertices.length; i++) { + attributes.pos[i * 2] = vertices[i].x + offset.x; + attributes.pos[i * 2 + 1] = vertices[i].y + offset.y; + if (opt.color) { + attributes.color[i * 3] = opt.color.r; + attributes.color[i * 3 + 1] = opt.color.g; + attributes.color[i * 3 + 2] = opt.color.b; + } + } + drawRaw( - verts, + attributes, indices, opt.fixed, gfx.defTex, @@ -441,8 +505,25 @@ export function _drawLinesRound(opt: DrawLinesOpt) { indices[index++] = vertices.length - 1; } + const attributes = { + pos: new Array(vertices.length * 2), + uv: new Array(vertices.length * 2).fill(0), + color: new Array(vertices.length * 3).fill(255), + opacity: new Array(vertices.length).fill(opt.opacity ?? 1), + }; + + for (let i = 0; i < vertices.length; i++) { + attributes.pos[i * 2] = vertices[i].x + offset.x; + attributes.pos[i * 2 + 1] = vertices[i].y + offset.y; + if (opt.color) { + attributes.color[i * 3] = opt.color.r; + attributes.color[i * 3 + 1] = opt.color.g; + attributes.color[i * 3 + 2] = opt.color.b; + } + } + drawRaw( - verts, + attributes, indices, opt.fixed, gfx.defTex, @@ -596,8 +677,25 @@ export function _drawLinesMiter(opt: DrawLinesOpt) { indices[index++] = vertices.length - 1; } + const attributes = { + pos: new Array(vertices.length * 2), + uv: new Array(vertices.length * 2).fill(0), + color: new Array(vertices.length * 3).fill(255), + opacity: new Array(vertices.length).fill(opt.opacity ?? 1), + }; + + for (let i = 0; i < vertices.length; i++) { + attributes.pos[i * 2] = vertices[i].x + offset.x; + attributes.pos[i * 2 + 1] = vertices[i].y + offset.y; + if (opt.color) { + attributes.color[i * 3] = opt.color.r; + attributes.color[i * 3 + 1] = opt.color.g; + attributes.color[i * 3 + 2] = opt.color.b; + } + } + drawRaw( - verts, + attributes, indices, opt.fixed, gfx.defTex, diff --git a/src/gfx/draw/drawPolygon.ts b/src/gfx/draw/drawPolygon.ts index 3acb082e..d16f9ba6 100644 --- a/src/gfx/draw/drawPolygon.ts +++ b/src/gfx/draw/drawPolygon.ts @@ -5,9 +5,9 @@ import type { DrawPolygonOpt } from "../../types"; import { popTransform, pushRotate, - pushScale, + pushScaleV, pushTransform, - pushTranslate, + pushTranslateV, } from "../stack"; import { drawLines } from "./drawLine"; import { drawRaw } from "./drawRaw"; @@ -24,15 +24,54 @@ export function drawPolygon(opt: DrawPolygonOpt) { } pushTransform(); - pushTranslate(opt.pos!); - pushScale(opt.scale); + pushTranslateV(opt.pos!); + pushScaleV(opt.scale); pushRotate(opt.angle); - pushTranslate(opt.offset!); + pushTranslateV(opt.offset!); if (opt.fill !== false) { const color = opt.color ?? Color.WHITE; - const verts = opt.pts.map((pt, i) => ({ + const attributes = { + pos: new Array(opt.pts.length * 2), + uv: new Array(opt.pts.length * 2), + color: new Array(opt.pts.length * 3), + opacity: new Array(opt.pts.length), + }; + + for (let i = 0; i < opt.pts.length; i++) { + attributes.pos[i * 2] = opt.pts[i].x; + attributes.pos[i * 2 + 1] = opt.pts[i].y; + } + + if (opt.uv) { + for (let i = 0; i < opt.uv.length; i++) { + attributes.uv[i * 2] = opt.uv[i].x; + attributes.uv[i * 2 + 1] = opt.uv[i].y; + } + } + else { + attributes.uv.fill(0); + } + + if (opt.colors) { + for (let i = 0; i < opt.colors.length; i++) { + attributes.color[i * 3] = opt.colors[i].r; + attributes.color[i * 3 + 1] = opt.colors[i].g; + attributes.color[i * 3 + 2] = opt.colors[i].b; + } + } + else { + for (let i = 0; i < opt.pts.length; i++) { + attributes.color[i * 3] = color.r; + attributes.color[i * 3 + 1] = color.g; + attributes.color[i * 3 + 2] = color.b; + } + } + + attributes.opacity.fill(opt.opacity ?? 1); + + /*const verts = opt.pts.map((pt, i) => ({ pos: new Vec2(pt.x, pt.y), uv: opt.uv ? opt.uv[i] @@ -41,7 +80,7 @@ export function drawPolygon(opt: DrawPolygonOpt) { ? (opt.colors[i] ? opt.colors[i].mult(color) : color) : color, opacity: opt.opacity ?? 1, - })); + }));*/ let indices; @@ -58,7 +97,7 @@ export function drawPolygon(opt: DrawPolygonOpt) { } drawRaw( - verts, + attributes, opt.indices ?? indices, opt.fixed, opt.uv ? opt.tex : gfx.defTex, diff --git a/src/gfx/draw/drawRaw.ts b/src/gfx/draw/drawRaw.ts index ef498a16..c2542782 100644 --- a/src/gfx/draw/drawRaw.ts +++ b/src/gfx/draw/drawRaw.ts @@ -1,16 +1,20 @@ import { Asset, resolveShader, type Uniform } from "../../assets"; import { game, gfx } from "../../kaplay"; +import { Vec2, vec2 } from "../../math/math"; import { screen2ndc } from "../../math/various"; -import type { RenderProps, Vertex } from "../../types"; +import type { Attributes, RenderProps } from "../../types"; import type { Texture } from "../gfx"; +import { height, width } from "../stack"; + +const scratchPt = new Vec2(); export function drawRaw( - verts: Vertex[], + attributes: Attributes, indices: number[], fixed: boolean = false, tex?: Texture, shaderSrc?: RenderProps["shader"], - uniform: Uniform = {}, + uniform?: Uniform, ) { const parsedTex = tex ?? gfx.defTex; const parsedShader = shaderSrc ?? gfx.defShader; @@ -22,24 +26,33 @@ export function drawRaw( const transform = (gfx.fixed || fixed) ? gfx.transform - : game.cam.transform.mult(gfx.transform); + : game.cam.transform.mul(gfx.transform); - const vv: number[] = []; + const vertLength = attributes.pos.length / 2; + const vv: number[] = new Array(vertLength * 8); - for (const v of verts) { + const w = width(); + const h = height(); + let index = 0; + for (let i = 0; i < vertLength; i++) { + scratchPt.x = attributes.pos[i * 2]; + scratchPt.y = attributes.pos[i * 2 + 1]; // normalized world space coordinate [-1.0 ~ 1.0] - const pt = screen2ndc(transform.multVec2(v.pos)); - - vv.push( - pt.x, - pt.y, - v.uv.x, - v.uv.y, - v.color.r / 255, - v.color.g / 255, - v.color.b / 255, - v.opacity, + screen2ndc( + transform.transformPoint(scratchPt, scratchPt), + w, + h, + scratchPt, ); + + vv[index++] = scratchPt.x; + vv[index++] = scratchPt.y; + vv[index++] = attributes.uv[i * 2]; + vv[index++] = attributes.uv[i * 2 + 1]; + vv[index++] = attributes.color[i * 3] / 255; + vv[index++] = attributes.color[i * 3 + 1] / 255; + vv[index++] = attributes.color[i * 3 + 2] / 255; + vv[index++] = attributes.opacity[i]; } gfx.renderer.push( diff --git a/src/gfx/draw/drawText.ts b/src/gfx/draw/drawText.ts index 72a566ce..6633ff41 100644 --- a/src/gfx/draw/drawText.ts +++ b/src/gfx/draw/drawText.ts @@ -80,10 +80,34 @@ export type CharTransformFunc = (idx: number, ch: string) => CharTransform; * @group Options */ export interface CharTransform { + /** + * Offset to apply to the position of the text character. + * Shifts the character's position by the specified 2D vector. + */ pos?: Vec2; + + /** + * Scale transformation to apply to the text character's current scale. + * When a number, it is scaled uniformly. + * Given a 2D vector, it is scaled independently along the X and Y axis. + */ scale?: Vec2 | number; + + /** + * Increases the amount of degrees to rotate the text character. + */ angle?: number; + + /** + * Color transformation applied to the text character. + * Multiplies the current color with this color. + */ color?: Color; + + /** + * Opacity multiplication applied to the text character. + * For example, an opacity of 0.4 with 2 set in the transformation, the resulting opacity will be 0.8 (0.4 × 2). + */ opacity?: number; } diff --git a/src/gfx/draw/drawTexture.ts b/src/gfx/draw/drawTexture.ts index 40d075af..a51401bd 100644 --- a/src/gfx/draw/drawTexture.ts +++ b/src/gfx/draw/drawTexture.ts @@ -14,13 +14,12 @@ export function drawTexture(opt: DrawTextureOpt) { const q = opt.quad ?? new Quad(0, 0, 1, 1); const w = opt.tex.width * q.w; const h = opt.tex.height * q.h; - const scale = new Vec2(1); + const scale = Vec2.ONE; if (opt.tiled) { - const anchor = anchorPt(opt.anchor || DEF_ANCHOR).add( - new Vec2(1, 1), - ).scale(0.5); - const offset = anchor.scale(opt.width || w, opt.height || h); + const offset = anchorPt(opt.anchor || DEF_ANCHOR); + const offsetX = (offset.x + 1) * 0.5 * (opt.width || w); + const offsetY = (offset.y + 1) * 0.5 * (opt.height || h); const fcols = (opt.width || w) / w; const frows = (opt.height || h) / h; @@ -30,13 +29,21 @@ export function drawTexture(opt: DrawTextureOpt) { const fracY = frows - rows; const n = (cols + fracX ? 1 : 0) * (rows + fracY ? 1 : 0); const indices = new Array(n * 6); - const vertices = new Array(n * 4); + const attributes = { + pos: new Array(n * 4 * 2), + uv: new Array(n * 4 * 2), + color: new Array(n * 4 * 3), + opacity: new Array(n * 4), + }; let index = 0; /*drawUVQuad(Object.assign({}, opt, { scale: scale.scale(opt.scale || new Vec2(1)), }));*/ + const color = opt.color || Color.WHITE; + const opacity = opt.opacity || 1; + const addQuad = ( x: number, y: number, @@ -51,30 +58,42 @@ export function drawTexture(opt: DrawTextureOpt) { indices[index * 6 + 4] = index * 4 + 2; indices[index * 6 + 5] = index * 4 + 3; - vertices[index * 4 + 0] = { - pos: new Vec2(x - offset.x, y - offset.y), - uv: new Vec2(q.x, q.y), - color: opt.color || Color.WHITE, - opacity: opt.opacity || 1, - }; - vertices[index * 4 + 1] = { - pos: new Vec2(x + w - offset.x, y - offset.y), - uv: new Vec2(q.x + q.w, q.y), - color: opt.color || Color.WHITE, - opacity: opt.opacity || 1, - }; - vertices[index * 4 + 2] = { - pos: new Vec2(x + w - offset.x, y + h - offset.y), - uv: new Vec2(q.x + q.w, q.y + q.h), - color: opt.color || Color.WHITE, - opacity: opt.opacity || 1, - }; - vertices[index * 4 + 3] = { - pos: new Vec2(x - offset.x, y + h - offset.y), - uv: new Vec2(q.x, q.y + q.h), - color: opt.color || Color.WHITE, - opacity: opt.opacity || 1, - }; + let s = index * 4; + attributes.pos[s * 2] = x - offsetX; + attributes.pos[s * 2 + 1] = y - offsetY; + attributes.uv[s * 2] = q.x; + attributes.uv[s * 2 + 1] = q.y; + attributes.color[s * 3] = color.r; + attributes.color[s * 3 + 1] = color.g; + attributes.color[s * 3 + 2] = color.b; + attributes.opacity[s] = opacity; + s++; + attributes.pos[s * 2] = x + w - offsetX; + attributes.pos[s * 2 + 1] = y - offsetY; + attributes.uv[s * 2] = q.x + q.w; + attributes.uv[s * 2 + 1] = q.y; + attributes.color[s * 3] = color.r; + attributes.color[s * 3 + 1] = color.g; + attributes.color[s * 3 + 2] = color.b; + attributes.opacity[s] = opacity; + s++; + attributes.pos[s * 2] = x + w - offsetX; + attributes.pos[s * 2 + 1] = y + h - offsetY; + attributes.uv[s * 2] = q.x + q.w; + attributes.uv[s * 2 + 1] = q.y + q.h; + attributes.color[s * 3] = color.r; + attributes.color[s * 3 + 1] = color.g; + attributes.color[s * 3 + 2] = color.b; + attributes.opacity[s] = opacity; + s++; + attributes.pos[s * 2] = x - offsetX; + attributes.pos[s * 2 + 1] = y + h - offsetY; + attributes.uv[s * 2] = q.x; + attributes.uv[s * 2 + 1] = q.y + q.h; + attributes.color[s * 3] = color.r; + attributes.color[s * 3 + 1] = color.g; + attributes.color[s * 3 + 2] = color.b; + attributes.opacity[s] = opacity; index++; }; @@ -117,7 +136,7 @@ export function drawTexture(opt: DrawTextureOpt) { } drawRaw( - vertices, + attributes, indices, opt.fixed, opt.tex, @@ -141,7 +160,7 @@ export function drawTexture(opt: DrawTextureOpt) { } drawUVQuad(Object.assign({}, opt, { - scale: scale.scale(opt.scale || new Vec2(1)), + scale: opt.scale ? scale.scale(opt.scale) : scale, tex: opt.tex, quad: q, width: w, diff --git a/src/gfx/draw/drawUVQuad.ts b/src/gfx/draw/drawUVQuad.ts index 6dd71d4f..db5f442f 100644 --- a/src/gfx/draw/drawUVQuad.ts +++ b/src/gfx/draw/drawUVQuad.ts @@ -1,14 +1,15 @@ import { DEF_ANCHOR, UV_PAD } from "../../constants"; -import { rgb } from "../../math/color"; +import { Color, rgb } from "../../math/color"; import { Quad, Vec2 } from "../../math/math"; import type { DrawUVQuadOpt } from "../../types"; import { anchorPt } from "../anchor"; import { popTransform, pushRotate, - pushScale, + pushScaleV, pushTransform, pushTranslate, + pushTranslateV, } from "../stack"; import { drawRaw } from "./drawRaw"; @@ -26,9 +27,10 @@ export function drawUVQuad(opt: DrawUVQuadOpt) { const w = opt.width; const h = opt.height; const anchor = anchorPt(opt.anchor || DEF_ANCHOR); - const offset = anchor.scale(new Vec2(w, h).scale(-0.5)); + const offsetX = anchor.x * w * -0.5; + const offsetY = anchor.y * h * -0.5; const q = opt.quad || new Quad(0, 0, 1, 1); - const color = opt.color || rgb(255, 255, 255); + const color = opt.color || Color.WHITE; const opacity = opt.opacity ?? 1; // apply uv padding to avoid artifacts @@ -40,50 +42,54 @@ export function drawUVQuad(opt: DrawUVQuadOpt) { const qh = q.h - uvPadY * 2; pushTransform(); - pushTranslate(opt.pos); + pushTranslateV(opt.pos); pushRotate(opt.angle); - pushScale(opt.scale); - pushTranslate(offset); + pushScaleV(opt.scale); + pushTranslate(offsetX, offsetY); drawRaw( - [ - { - pos: new Vec2(-w / 2, h / 2), - uv: new Vec2( - opt.flipX ? qx + qw : qx, - opt.flipY ? qy : qy + qh, - ), - color: color, - opacity: opacity, - }, - { - pos: new Vec2(-w / 2, -h / 2), - uv: new Vec2( - opt.flipX ? qx + qw : qx, - opt.flipY ? qy + qh : qy, - ), - color: color, - opacity: opacity, - }, - { - pos: new Vec2(w / 2, -h / 2), - uv: new Vec2( - opt.flipX ? qx : qx + qw, - opt.flipY ? qy + qh : qy, - ), - color: color, - opacity: opacity, - }, - { - pos: new Vec2(w / 2, h / 2), - uv: new Vec2( - opt.flipX ? qx : qx + qw, - opt.flipY ? qy : qy + qh, - ), - color: color, - opacity: opacity, - }, - ], + { + pos: [ + -w / 2, + h / 2, + -w / 2, + -h / 2, + w / 2, + -h / 2, + w / 2, + h / 2, + ], + uv: [ + opt.flipX ? qx + qw : qx, + opt.flipY ? qy : qy + qh, + opt.flipX ? qx + qw : qx, + opt.flipY ? qy + qh : qy, + opt.flipX ? qx : qx + qw, + opt.flipY ? qy + qh : qy, + opt.flipX ? qx : qx + qw, + opt.flipY ? qy : qy + qh, + ], + color: [ + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + color.r, + color.g, + color.b, + ], + opacity: [ + opacity, + opacity, + opacity, + opacity, + ], + }, [0, 1, 3, 1, 2, 3], opt.fixed, opt.tex, diff --git a/src/gfx/formatText.ts b/src/gfx/formatText.ts index ccfc1065..0bacef82 100644 --- a/src/gfx/formatText.ts +++ b/src/gfx/formatText.ts @@ -4,7 +4,6 @@ import { DEF_TEXT_CACHE_SIZE, FONT_ATLAS_HEIGHT, FONT_ATLAS_WIDTH, - MULTI_WORD_RE, } from "../constants"; import { fontCacheC2d, fontCacheCanvas, gfx } from "../kaplay"; import { Color } from "../math/color"; @@ -39,71 +38,70 @@ function applyCharTransform(fchar: FormattedChar, tr: CharTransform) { if (tr.opacity != null) fchar.opacity *= tr.opacity; } -export function compileStyledText(text: string): { +export function compileStyledText(txt: string): { charStyleMap: Record; text: string; } { const charStyleMap = {} as Record; let renderText = ""; - let styleStack: [string, number][] = []; - let lastIndex = 0; - let skipCount = 0; + let styleStack: string[] = []; + let text = String(txt); - for (let i = 0; i < text.length; i++) { - if (i !== lastIndex + 1) skipCount += i - lastIndex; - lastIndex = i; - - if (text[i] === "\\" && text[i + 1] === "[") continue; - - if ((i === 0 || text[i - 1] !== "\\") && text[i] === "[") { - const start = i; - - i++; - - let isClosing = text[i] === "/"; - let style = ""; - - if (isClosing) i++; - - while (i < text.length && text[i] !== "]") { - style += text[i++]; - } + const emit = (ch: string) => { + if (styleStack.length > 0) { + charStyleMap[renderText.length] = styleStack.slice(); + } + renderText += ch; + }; - if ( - !MULTI_WORD_RE.test(style) - || i >= text.length - || text[i] !== "]" - || (isClosing - && (styleStack.length === 0 - || styleStack[styleStack.length - 1][0] !== style)) - ) { - i = start; + while (text !== "") { + if (text[0] === "\\") { + if (text.length === 1) { + throw new Error("Styled text error: \\ at end of string"); } - else { - if (!isClosing) styleStack.push([style, start]); - else styleStack.pop(); - + emit(text[1]); + text = text.slice(2); + continue; + } + if (text[0] === "[") { + const execResult = /^\[(\/)?(\w+?)\]/.exec(text); + if (!execResult) { + // xxx: should throw an error here? + emit(text[0]); + text = text.slice(1); continue; } + const [m, e, gn] = execResult; + if (e !== undefined) { + const x = styleStack.pop(); + if (x !== gn) { + if (x !== undefined) { + throw new Error( + "Styled text error: mismatched tags. " + + `Expected [/${x}], got [/${gn}]`, + ); + } + else {throw new Error( + `Styled text error: stray end tag [/${gn}]`, + );} + } + } + else styleStack.push(gn); + text = text.slice(m.length); + continue; } - - renderText += text[i]; - if (styleStack.length > 0) { - charStyleMap[i - skipCount] = styleStack.map(([name]) => name); - } + emit(text[0]); + text = text.slice(1); } if (styleStack.length > 0) { - while (styleStack.length > 0) { - const [_, start] = styleStack.pop()!; - text = text.substring(0, start) + "\\" + text.substring(start); - } - - return compileStyledText(text); + throw new Error( + `Styled text error: unclosed tags ${styleStack}`, + ); } return { - charStyleMap: charStyleMap, + charStyleMap, text: renderText, }; } diff --git a/src/gfx/gfx.ts b/src/gfx/gfx.ts index 08946254..8cb0fc03 100644 --- a/src/gfx/gfx.ts +++ b/src/gfx/gfx.ts @@ -13,6 +13,7 @@ export class Texture { constructor(ctx: GfxCtx, w: number, h: number, opt: TextureOpt = {}) { this.ctx = ctx; + const gl = ctx.gl; const glText = ctx.gl.createTexture(); @@ -99,131 +100,6 @@ export class Texture { } } -/** - * @group GFX - */ -export class FrameBuffer { - ctx: GfxCtx; - tex: Texture; - glFramebuffer: WebGLFramebuffer; - glRenderbuffer: WebGLRenderbuffer; - - constructor(ctx: GfxCtx, w: number, h: number, opt: TextureOpt = {}) { - this.ctx = ctx; - const gl = ctx.gl; - ctx.onDestroy(() => this.free()); - this.tex = new Texture(ctx, w, h, opt); - - const frameBuffer = gl.createFramebuffer(); - const renderBuffer = gl.createRenderbuffer(); - - if (!frameBuffer || !renderBuffer) { - throw new Error("Failed to create framebuffer"); - } - - this.glFramebuffer = frameBuffer; - this.glRenderbuffer = renderBuffer; - - this.bind(); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h); - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - this.tex.glTex, - 0, - ); - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.DEPTH_STENCIL_ATTACHMENT, - gl.RENDERBUFFER, - this.glRenderbuffer, - ); - this.unbind(); - } - - get width() { - return this.tex.width; - } - - get height() { - return this.tex.height; - } - - toImageData() { - const gl = this.ctx.gl; - const data = new Uint8ClampedArray(this.width * this.height * 4); - this.bind(); - gl.readPixels( - 0, - 0, - this.width, - this.height, - gl.RGBA, - gl.UNSIGNED_BYTE, - data, - ); - this.unbind(); - // flip vertically - const bytesPerRow = this.width * 4; - const temp = new Uint8Array(bytesPerRow); - for (let y = 0; y < (this.height / 2 | 0); y++) { - const topOffset = y * bytesPerRow; - const bottomOffset = (this.height - y - 1) * bytesPerRow; - temp.set(data.subarray(topOffset, topOffset + bytesPerRow)); - data.copyWithin( - topOffset, - bottomOffset, - bottomOffset + bytesPerRow, - ); - data.set(temp, bottomOffset); - } - return new ImageData(data, this.width, this.height); - } - - toDataURL() { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); - canvas.width = this.width; - canvas.height = this.height; - - if (!ctx) throw new Error("Failed to get 2d context"); - - ctx.putImageData(this.toImageData(), 0, 0); - return canvas.toDataURL(); - } - - clear() { - const gl = this.ctx.gl; - gl.clear(gl.COLOR_BUFFER_BIT); - } - - draw(action: () => void) { - this.bind(); - action(); - this.unbind(); - } - - bind() { - this.ctx.pushFramebuffer(this.glFramebuffer); - this.ctx.pushRenderbuffer(this.glRenderbuffer); - this.ctx.pushViewport({ x: 0, y: 0, w: this.width, h: this.height }); - } - - unbind() { - this.ctx.popFramebuffer(); - this.ctx.popRenderbuffer(); - this.ctx.popViewport(); - } - - free() { - const gl = this.ctx.gl; - gl.deleteFramebuffer(this.glFramebuffer); - gl.deleteRenderbuffer(this.glRenderbuffer); - this.tex.free(); - } -} - export type VertexFormat = { name: string; size: number; @@ -246,7 +122,7 @@ export class BatchRenderer { curPrimitive: GLenum | null = null; curTex: Texture | null = null; curShader: Shader | null = null; - curUniform: Uniform = {}; + curUniform: Uniform | null = null; constructor( ctx: GfxCtx, @@ -286,13 +162,14 @@ export class BatchRenderer { indices: number[], shader: Shader, tex: Texture | null = null, - uniform: Uniform = {}, + uniform: Uniform | null = null, ) { if ( primitive !== this.curPrimitive || tex !== this.curTex || shader !== this.curShader - || !deepEq(this.curUniform, uniform) + || ((this.curUniform != uniform) + && !deepEq(this.curUniform, uniform)) || this.vqueue.length + verts.length * this.stride > this.maxVertices || this.iqueue.length + indices.length > this.maxIndices @@ -300,11 +177,13 @@ export class BatchRenderer { this.flush(); } const indexOffset = this.vqueue.length / this.stride; - for (const v of verts) { - this.vqueue.push(v); + let l = verts.length; + for (let i = 0; i < l; i++) { + this.vqueue.push(verts[i]); } - for (const i of indices) { - this.iqueue.push(i + indexOffset); + l = indices.length; + for (let i = 0; i < l; i++) { + this.iqueue.push(indices[i] + indexOffset); } this.curPrimitive = primitive; this.curShader = shader; @@ -334,7 +213,9 @@ export class BatchRenderer { ); this.ctx.setVertexFormat(this.vertexFormat); this.curShader.bind(); - this.curShader.send(this.curUniform); + if (this.curUniform) { + this.curShader.send(this.curUniform); + } this.curTex?.bind(); gl.drawElements( this.curPrimitive, diff --git a/src/gfx/gfxApp.ts b/src/gfx/gfxApp.ts index b786f166..3f585af0 100644 --- a/src/gfx/gfxApp.ts +++ b/src/gfx/gfxApp.ts @@ -8,7 +8,7 @@ import { } from "../constants"; import { BatchRenderer, FrameBuffer, type GfxCtx, Texture } from "../gfx"; import { type Color, rgb } from "../math/color"; -import { Mat4 } from "../math/math"; +import { Mat23, Mat4 } from "../math/math"; import type { KAPLAYOpt } from "../types"; export type AppGfxCtx = ReturnType; @@ -104,6 +104,8 @@ export const initAppGfx = (gopt: KAPLAYOpt, ggl: GfxCtx) => { }, ); + const transformStack = new Array(32).fill(0).map(_ => new Mat23()); + return { // how many draw calls we're doing last frame, this is the number we give to users lastDrawCalls: 0, @@ -117,8 +119,9 @@ export const initAppGfx = (gopt: KAPLAYOpt, ggl: GfxCtx) => { postShaderUniform: null as Uniform | (() => Uniform) | null, renderer: renderer, - transform: new Mat4(), - transformStack: [] as Mat4[], + transform: new Mat23(), + transformStack: transformStack, + transformStackIndex: -1, bgTex: bgTex, bgColor: bgColor, diff --git a/src/gfx/index.ts b/src/gfx/index.ts index 1595f8f5..c6305621 100644 --- a/src/gfx/index.ts +++ b/src/gfx/index.ts @@ -1,9 +1,10 @@ export * from "./anchor"; export * from "./bg"; +export * from "./classes/FrameBuffer"; +export * from "./classes/TexPacker"; export * from "./draw"; export * from "./formatText"; export * from "./gfx"; export * from "./gfxApp"; export * from "./stack"; -export * from "./texPacker"; export * from "./viewport"; diff --git a/src/gfx/stack.ts b/src/gfx/stack.ts index 09e9f42e..917c838a 100644 --- a/src/gfx/stack.ts +++ b/src/gfx/stack.ts @@ -1,42 +1,45 @@ import { app, gfx } from "../kaplay"; -import { type Mat4, Vec2, vec2, type Vec2Args } from "../math/math"; +import { type Mat23, Vec2, vec2, type Vec2Args } from "../math/math"; -export function pushTranslate(...args: Vec2Args | [undefined]) { - if (args[0] === undefined) return; +export function pushTranslateV(t: Vec2 | undefined) { + if (t === undefined) return; + if (t.x === 0 && t.y === 0) return; + gfx.transform.translateSelfV(t); +} - const p = vec2(...args); - if (p.x === 0 && p.y === 0) return; - gfx.transform.translate(p); +export function pushTranslate(x: number, y: number) { + if (x === 0 && y === 0) return; + gfx.transform.translateSelf(x, y); } export function pushTransform() { - gfx.transformStack.push(gfx.transform.clone()); + gfx.transformStack[++gfx.transformStackIndex].setMat23(gfx.transform); } -export function pushMatrix(m: Mat4) { - gfx.transform = m.clone(); +export function pushMatrix(m: Mat23) { + gfx.transform.setMat23(m); } -export function pushScale( - ...args: Vec2Args | [undefined] | [undefined, undefined] -) { - if (args[0] === undefined) return; +export function pushScaleV(s: Vec2 | undefined) { + if (s === undefined) return; + if (s.x === 1 && s.y === 1) return; + gfx.transform.scaleSelfV(s); +} - const p = vec2(...args); - if (p.x === 1 && p.y === 1) return; - gfx.transform.scale(p); +export function pushScale(x: number, y: number) { + if (x === 1 && y === 1) return; + gfx.transform.scaleSelf(x, y); } export function pushRotate(a: number | undefined) { if (!a) return; - gfx.transform.rotate(a); + gfx.transform.rotateSelf(a); } export function popTransform() { - if (gfx.transformStack.length > 0) { - // if there's more than 1 element, it will return obviously a Mat4 - gfx.transform = gfx.transformStack.pop()!; + if (gfx.transformStackIndex >= 0) { + gfx.transform.setMat23(gfx.transformStack[gfx.transformStackIndex--]); } } diff --git a/src/index.ts b/src/index.ts index 910d9675..54ee56cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,16 +61,16 @@ export type { LifespanCompOpt, MaskComp, NamedComp, - NavigationComp, - NavigationCompOpt, - NavigationMapComp, - NavigationMapCompOpt, OffScreenComp, OffScreenCompOpt, OpacityComp, OutlineComp, ParticlesComp, ParticlesOpt, + PathfinderComp, + PathfinderCompOpt, + PathfinderMapComp, + PathfinderMapCompOpt, PatrolComp, PatrolCompOpt, PlatformEffectorComp, diff --git a/src/kaplay.ts b/src/kaplay.ts index 341ed7ab..80d43cbf 100644 --- a/src/kaplay.ts +++ b/src/kaplay.ts @@ -36,9 +36,9 @@ import { popTransform, pushMatrix, pushRotate, - pushScale, + pushScaleV, pushTransform, - pushTranslate, + pushTranslateV, setBackground, updateViewport, width, @@ -91,6 +91,8 @@ import { chooseMultiple, Circle, clamp, + clipLineToCircle, + clipLineToRect, Color, curveLengthApproximation, deg2rad, @@ -116,6 +118,7 @@ import { Line, map, mapc, + Mat23, Mat4, NavMesh, normalizedCurve, @@ -132,6 +135,7 @@ import { RNG, sat, shuffle, + SweepAndPrune, testCirclePolygon, testLineCircle, testLineLine, @@ -193,14 +197,14 @@ import { mask, move, named, - navigation, offscreen, opacity, outline, particles, + pathfinder, patrol, - pointEffector, platformEffector, + pointEffector, polygon, pos, raycast, @@ -441,6 +445,7 @@ const kaplay = < function makeCanvas(w: number, h: number) { const fb = new FrameBuffer(ggl, w, h); + return { clear: () => fb.clear(), free: () => fb.free(), @@ -485,8 +490,8 @@ const kaplay = < gfx.renderer.numDraws = 0; gfx.fixed = false; - gfx.transformStack.length = 0; - gfx.transform = new Mat4(); + gfx.transformStackIndex = -1; + gfx.transform.setIdentity(); } function usePostEffect(name: string, uniform?: Uniform | (() => Uniform)) { @@ -543,7 +548,7 @@ const kaplay = < clearLog: () => game.logs = [], log: (...msgs) => { const max = gopt.logMax ?? LOG_MAX; - const msg = msgs.concat(" ").join(" "); + const msg = msgs.length > 1 ? msgs.concat(" ").join(" ") : msgs[0]; game.logs.unshift({ msg: msg, @@ -686,8 +691,8 @@ const kaplay = < const query = game.root.query.bind(game.root); const tween = game.root.tween.bind(game.root); - kaSprite = loadSprite(null, kaSpriteSrc); boomSprite = loadSprite(null, boomSpriteSrc); + kaSprite = loadSprite(null, kaSpriteSrc); function fixedUpdateFrame() { // update every obj @@ -750,18 +755,90 @@ const kaplay = < } } + function narrowPhase( + obj: GameObj, + other: GameObj, + ): boolean { + if (other.paused) return false; + if (!other.exists()) return false; + for (const tag of obj.collisionIgnore) { + if (other.is(tag)) { + return false; + } + } + for (const tag of other.collisionIgnore) { + if (obj.is(tag)) { + return false; + } + } + const res = gjkShapeIntersection( + obj.worldArea(), + other.worldArea(), + ); + if (res) { + const col1 = new Collision( + obj, + other, + res.normal, + res.distance, + ); + obj.trigger("collideUpdate", other, col1); + const col2 = col1.reverse(); + // resolution only has to happen once + col2.resolved = col1.resolved; + other.trigger("collideUpdate", obj, col2); + } + return true; + } + + const sap = new SweepAndPrune(); + let sapInit = false; + function broadPhase() { + if (!usesArea()) { + return; + } + + if (!sapInit) { + sapInit = true; + onAdd(obj => { + if (obj.is("area")) { + sap.add(obj as GameObj); + } + }); + onDestroy(obj => { + sap.remove(obj as GameObj); + }); + onSceneLeave(scene => { + sapInit = false; + sap.clear(); + }); + for (const obj of get("*", { recursive: true })) { + if (obj.is("area")) { + sap.add(obj as GameObj); + } + } + } + + sap.update(); + for (const [obj1, obj2] of sap) { + narrowPhase(obj1, obj2); + } + } + function checkFrame() { if (!usesArea()) { return; } - // TODO: persistent grid? + return broadPhase(); + + /*// TODO: persistent grid? // start a spatial hash grid for more efficient collision detection const grid: Record[]>> = {}; const cellSize = gopt.hashGridSize || DEF_HASH_GRID_SIZE; // current transform - let tr = new Mat4(); + let tr = new Mat23(); // a local transform stack const stack: any[] = []; @@ -821,7 +898,6 @@ const kaplay = < other.worldArea(), ); if (res) { - // console.log(res) // TODO: rehash if the object position is changed after resolution? const col1 = new Collision( aobj, @@ -847,7 +923,7 @@ const kaplay = < tr = stack.pop(); } - checkObj(game.root); + checkObj(game.root);*/ } function handleErr(err: Error) { @@ -1130,7 +1206,7 @@ const kaplay = < agent, sentry, patrol, - navigation, + pathfinder, fakeMouse, // group events on, @@ -1188,6 +1264,8 @@ const kaplay = < isButtonReleased: app.isButtonReleased, setButton: app.setButton, getButton: app.getButton, + pressButton: app.pressButton, + releaseButton: app.releaseButton, getLastInputDeviceType: app.getLastInputDeviceType, charInputted: app.charInputted, // timer @@ -1208,6 +1286,7 @@ const kaplay = < Vec2, Color, Mat4, + Mat23, Quad, RNG, rand, @@ -1255,6 +1334,8 @@ const kaplay = < testCirclePolygon, testLinePoint, testLineCircle, + clipLineToRect, + clipLineToCircle, gjkShapeIntersects, gjkShapeIntersection, isConvex, @@ -1279,8 +1360,8 @@ const kaplay = < drawSubtracted, pushTransform, popTransform, - pushTranslate, - pushScale, + pushTranslate: pushTranslateV, + pushScale: pushScaleV, pushRotate, pushMatrix, usePostEffect, diff --git a/src/math/index.ts b/src/math/index.ts index 8df5cbc0..742b49ae 100644 --- a/src/math/index.ts +++ b/src/math/index.ts @@ -6,5 +6,6 @@ export * from "./navigation"; export * from "./navigationgrid"; export * from "./navigationmesh"; export * from "./sat"; +export * from "./spatial/sweepandprune"; export * from "./various"; export * from "./vec3"; diff --git a/src/math/math.ts b/src/math/math.ts index 3da86076..8ab4ff3d 100644 --- a/src/math/math.ts +++ b/src/math/math.ts @@ -107,6 +107,8 @@ export class Vec2 { return new Vec2(arr[0], arr[1]); } + static ZERO = new Vec2(0, 0); + static ONE = new Vec2(1, 1); static LEFT = new Vec2(-1, 0); static RIGHT = new Vec2(1, 0); static UP = new Vec2(0, -1); @@ -114,9 +116,11 @@ export class Vec2 { /** Closest orthogonal direction: LEFT, RIGHT, UP, or DOWN */ toAxis(): Vec2 { - return Math.abs(this.x) > Math.abs(this.y) ? - this.x < 0 ? Vec2.LEFT : Vec2.RIGHT : - this.y < 0 ? Vec2.UP : Vec2.DOWN; + return Math.abs(this.x) > Math.abs(this.y) + ? this.x < 0 ? Vec2.LEFT : Vec2.RIGHT + : this.y < 0 + ? Vec2.UP + : Vec2.DOWN; } /** Clone the vector */ @@ -124,40 +128,180 @@ export class Vec2 { return new Vec2(this.x, this.y); } - /** Returns the addition with another vector. */ + static copy(v: Vec2, out: Vec2): Vec2 { + out.x = v.x; + out.y = v.y; + return out; + } + + /** Returns the sum with another vector. */ add(...args: Vec2Args): Vec2 { const p2 = vec2(...args); return new Vec2(this.x + p2.x, this.y + p2.y); } - /** Returns the subtraction with another vector. */ + static addScaled(v: Vec2, other: Vec2, s: number, out: Vec2): Vec2 { + out.x = v.x + other.x * s; + out.y = v.y + other.y * s; + return out; + } + + /** + * Calculates the sum of the vectors + * @param v The first term + * @param x The x of the second term + * @param y The y of the second term + * @param out The vector sum + * @returns The sum of the vectors + */ + static addc(v: Vec2, x: number, y: number, out: Vec2): Vec2 { + out.x = v.x + x; + out.y = v.y + y; + return out; + } + + /** + * Calculates the sum of the vectors + * @param v The first term + * @param other The second term + * @param out The vector sum + * @returns The sum of the vectors + */ + static add(v: Vec2, other: Vec2, out: Vec2): Vec2 { + out.x = v.x + other.x; + out.y = v.y + other.y; + return out; + } + + /** Returns the difference with another vector. */ sub(...args: Vec2Args): Vec2 { const p2 = vec2(...args); return new Vec2(this.x - p2.x, this.y - p2.y); } + /** + * Calculates the difference of the vectors + * @param v The first term + * @param x The x of the second term + * @param y The y of the second term + * @param out The vector difference + * @returns The difference of the vectors + */ + static subc(v: Vec2, x: number, y: number, out: Vec2): Vec2 { + out.x = v.x - x; + out.y = v.y - y; + return out; + } + + /** + * Calculates the difference of the vectors + * @param v The first term + * @param other The second term + * @param out The vector difference + * @returns The difference of the vectors + */ + static sub(v: Vec2, other: Vec2, out: Vec2): Vec2 { + out.x = v.x - other.x; + out.y = v.y - other.y; + return out; + } + /** Scale by another vector. or a single number */ scale(...args: Vec2Args): Vec2 { const s = vec2(...args); return new Vec2(this.x * s.x, this.y * s.y); } + /** + * Calculates the scale of the vector + * @param v The vector + * @param x The x scale + * @param y The y scale + * @param out The scaled vector + * @returns The scale of the vector + */ + static scale(v: Vec2, s: number, out: Vec2): Vec2 { + out.x = v.x * s; + out.y = v.y * s; + return out; + } + + /** + * Calculates the scale of the vector + * @param v The vector + * @param x The x scale + * @param y The y scale + * @param out The scaled vector + * @returns The scale of the vector + */ + static scalec(v: Vec2, x: number, y: number, out: Vec2): Vec2 { + out.x = v.x * x; + out.y = v.y * y; + return out; + } + + /** + * Calculates the scale of the vector + * @param v The vector + * @param other The scale + * @param out The scaled vector + * @returns The scale of the vector + */ + static scalev(v: Vec2, other: Vec2, out: Vec2): Vec2 { + out.x = v.x * other.x; + out.y = v.y * other.y; + return out; + } + /** Get distance between another vector */ dist(...args: Vec2Args): number { const p2 = vec2(...args); return this.sub(p2).len(); } + /** + * Calculates the distance between the vectors + * @param v The vector + * @param other The other vector + * @returns The between the vectors + */ + static dist(v: Vec2, other: Vec2): number { + const x = v.x - other.x; + const y = v.y - other.y; + return Math.sqrt(x * x + y * y); + } + /** Get squared distance between another vector */ sdist(...args: Vec2Args): number { const p2 = vec2(...args); return this.sub(p2).slen(); } + /** + * Calculates the squared distance between the vectors + * @param v The vector + * @param other The other vector + * @returns The distance between the vectors + */ + static sdist(v: Vec2, other: Vec2): number { + const x = v.x - other.x; + const y = v.y - other.y; + return x * x + y * y; + } + len(): number { return Math.sqrt(this.dot(this)); } + /** + * Calculates the length of the vector + * @param v The vector + * @returns The length of the vector + */ + static len(v: Vec2) { + return Math.sqrt(v.x * v.x + v.y * v.y); + } + /** * Get squared length of the vector * @@ -167,6 +311,15 @@ export class Vec2 { return this.dot(this); } + /** + * Calculates the squared length of the vector + * @param v The vector + * @returns The squared length of the vector + */ + static slen(v: Vec2) { + return v.x * v.x + v.y * v.y; + } + /** * Get the unit vector (length of 1). */ @@ -175,6 +328,13 @@ export class Vec2 { return len === 0 ? new Vec2(0) : this.scale(1 / len); } + static unit(v: Vec2, out: Vec2): Vec2 { + const len = Vec2.len(v); + out.x = v.x / len; + out.y = v.y / len; + return out; + } + /** * Get the perpendicular vector. */ @@ -182,6 +342,12 @@ export class Vec2 { return new Vec2(this.y, -this.x); } + static normal(v: Vec2, out: Vec2): Vec2 { + out.x = v.y; + out.y = -v.x; + return out; + } + /** * Get the reflection of a vector with a normal. * @@ -227,6 +393,36 @@ export class Vec2 { } } + /** + * Calculates the rotated vector + * @param v The vector + * @param dir The rotation vector + * @param out The rotated vector + * @returns The rotated vector + */ + static rotate(v: Vec2, dir: Vec2, out: Vec2): Vec2 { + const tmp = v.x; + out.x = v.x * dir.x - v.y * dir.y; + out.y = tmp * dir.y + v.y * dir.x; + return out; + } + + /** + * Calculates the rotated vector + * @param v The vector + * @param angle The angle in radians + * @param out The rotated vector + * @returns The rotated vector + */ + static rotateByAngle(v: Vec2, angle: number, out: Vec2): Vec2 { + const c = Math.cos(angle); + const s = Math.sin(angle); + const tmp = v.x; + out.x = v.x * c - v.y * s; + out.y = tmp * s + v.y * c; + return out; + } + invRotate(vecOrAngle: Vec2 | number) { if (vecOrAngle instanceof Vec2) { return this.rotate(new Vec2(vecOrAngle.x, -vecOrAngle.y)); @@ -236,6 +432,20 @@ export class Vec2 { } } + /** + * Calculates the inverse rotated vector + * @param v The vector + * @param dir The rotation vector + * @param out The rotated vector + * @returns The rotated vector + */ + static inverseRotate(v: Vec2, dir: Vec2, out: Vec2): Vec2 { + const tmp = v.x; + out.x = v.x * dir.x + v.y * dir.y; + out.y = -tmp * dir.y + v.y * dir.x; + return out; + } + /** * Get the dot product with another vector. */ @@ -243,6 +453,10 @@ export class Vec2 { return this.x * p2.x + this.y * p2.y; } + static dot(v: Vec2, other: Vec2): number { + return v.x * v.x + v.y * v.y; + } + /** * Get the cross product with another vector. * @@ -252,6 +466,10 @@ export class Vec2 { return this.x * p2.y - this.y * p2.x; } + static cross(v: Vec2, other: Vec2): number { + return v.x * other.y - v.y * other.x; + } + /** * Get the angle of the vector in degrees. */ @@ -260,6 +478,15 @@ export class Vec2 { return rad2deg(Math.atan2(this.y - p2.y, this.x - p2.x)); } + /** + * Calculates the angle represented by the vector in radians + * @param v The vector + * @returns Angle represented by the vector in radians + */ + static toAngle(v: Vec2) { + return Math.atan2(v.y, v.x); + } + /** * Get the angle between this vector and another vector. * @@ -270,6 +497,16 @@ export class Vec2 { return rad2deg(Math.atan2(this.cross(p2), this.dot(p2))); } + /** + * Calculates the angle between the vectors in radians + * @param v First vector + * @param other Second vector + * @returns Angle between the vectors in radians + */ + static angleBetween(v: Vec2, other: Vec2) { + return Math.atan2(Vec2.cross(v, other), Vec2.dot(v, other)); + } + /** * Linear interpolate to a destination vector (for positions). */ @@ -277,6 +514,20 @@ export class Vec2 { return new Vec2(lerp(this.x, dest.x, t), lerp(this.y, dest.y, t)); } + /** + * Linear interpolate src and dst by t + * @param src First vector + * @param dst Second vector + * @param t Percentage + * @param out The linear interpolation between src and dst by t + * @returns The linear interpolation between src and dst by t + */ + static lerp(src: Vec2, dst: Vec2, t: number, out: Vec2): Vec2 { + out.x = src.x * (dst.x - src.x) * t; + out.y = src.y * (dst.y - src.y) * t; + return out; + } + /** * Spherical linear interpolate to a destination vector (for rotations). * @@ -292,6 +543,26 @@ export class Vec2 { .scale(1 / sin); } + /** + * Spherical interpolate src and dst by t + * @param src First vector + * @param dst Second vector + * @param t Percentage + * @param out The spherical interpolation between src and dst by t + * @returns The spherical interpolation between src and dst by t + */ + static slerp(src: Vec2, dst: Vec2, t: number, out: Vec2): Vec2 { + const cos = Vec2.dot(src, dst); + const sin = Vec2.cross(src, dst); + const angle = Math.atan2(sin, cos); + const t1 = Math.sin((1 - t) * angle); + const t2 = Math.sin(t * angle); + const invSin = 1 / sin; + out.x = (src.x * t1 + dst.x * t2) * invSin; + out.y = (src.y * t1 + dst.y * t2) * invSin; + return out; + } + /** * If the vector (x, y) is zero. * @@ -490,7 +761,7 @@ class Mat2 { } // Internal class -class Mat23 { +export class Mat23 { // 2x3 matrix, since the last column is always (0, 0, 1) a: number; b: number; // 0 @@ -563,6 +834,32 @@ class Mat23 { 0, ); } + clone() { + return new Mat23( + this.a, + this.b, + this.c, + this.d, + this.e, + this.f, + ); + } + setMat23(m: Mat23) { + this.a = m.a; + this.b = m.b; + this.c = m.c; + this.d = m.d; + this.e = m.e; + this.f = m.f; + } + setIdentity() { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.e = 0; + this.f = 0; + } mul(other: Mat23): Mat23 { return new Mat23( other.a * this.a + other.b * this.c, @@ -573,12 +870,18 @@ class Mat23 { other.e * this.b + other.f * this.d + this.f, ); } - translate(t: Vec2): Mat23 { + translateSelfV(t: Vec2): Mat23 { this.e += t.x * this.a + t.y * this.c; - this.f += t.y * this.b + t.x * this.d; + this.f += t.x * this.b + t.y * this.d; return this; } - rotate(radians: number): Mat23 { + translateSelf(x: number, y: number): Mat23 { + this.e += x * this.a + y * this.c; + this.f += x * this.b + y * this.d; + return this; + } + rotateSelf(degrees: number): Mat23 { + const radians = deg2rad(degrees); const c = Math.cos(radians); const s = Math.sin(radians); const oldA = this.a; @@ -589,19 +892,52 @@ class Mat23 { this.d = c * this.d - s * oldB; return this; } - scale(s: Vec2): Mat23 { + scaleSelfV(s: Vec2): Mat23 { this.a *= s.x; this.b *= s.x; this.c *= s.y; this.d *= s.y; return this; } + scaleSelf(x: number, y: number): Mat23 { + this.a *= x; + this.b *= x; + this.c *= y; + this.d *= y; + return this; + } + mulSelf(other: Mat23) { + const a = other.a * this.a + other.b * this.c; + const b = other.a * this.b + other.b * this.d; + const c = other.c * this.a + other.d * this.c; + const d = other.c * this.b + other.d * this.d; + const e = other.e * this.a + other.f * this.c + this.e; + const f = other.e * this.b + other.f * this.d + this.f; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } transform(p: Vec2) { return vec2( this.a * p.x + this.c * p.y + this.e, this.b * p.x + this.d * p.y + this.f, ); } + transformPoint(p: Vec2, o: Vec2): Vec2 { + const tmp = p.x; + o.x = this.a * p.x + this.c * p.y + this.e; + o.y = this.b * tmp + this.d * p.y + this.f; + return o; + } + transformVector(v: Vec2, o: Vec2): Vec2 { + const tmp = v.x; + o.x = this.a * v.x + this.c * v.y; + o.y = this.b * tmp + this.d * v.y; + return o; + } get det() { return this.a * this.d - this.b * this.c; @@ -618,6 +954,20 @@ class Mat23 { (this.b * this.e - this.a * this.f) / det, ); } + getTranslation() { + return new Vec2(this.e, this.f); + } + getRotation() { + return rad2deg( + Math.atan2(-this.c, this.a), + ); + } + getScale() { + return new Vec2( + Math.sqrt(this.a * this.a + this.c * this.c), + Math.sqrt(this.b * this.b + this.d * this.d), + ); + } } // Internal class @@ -1166,9 +1516,8 @@ export function rand(...args: [] | [T] | [T, T]) { return defRNG.genAny(...args); } -// TODO: randi() to return 0 / 1? export function randi(...args: [] | [number] | [number, number]) { - return Math.floor(rand(...args)); + return Math.floor(rand(...(args.length > 0 ? args : [2]))); } export function chance(p: number): boolean { @@ -1247,15 +1596,47 @@ export function testLineLine(l1: Line, l2: Line): Vec2 | null { ); } +export function clipLineToRect(r: Rect, l: Line, result: Line): boolean { + const dir = l.p2.sub(l.p1); + let tmin = Number.NEGATIVE_INFINITY, tmax = Number.POSITIVE_INFINITY; + + if (dir.x != 0.0) { + const tx1 = (r.pos.x - l.p1.x) / dir.x; + const tx2 = (r.pos.x + r.width - l.p1.x) / dir.x; + + tmin = Math.max(tmin, Math.min(tx1, tx2)); + tmax = Math.min(tmax, Math.max(tx1, tx2)); + } + else { + if (l.p1.x < r.pos.x || l.p1.x > r.pos.x + r.width) { + return false; + } + } + + if (dir.y != 0.0) { + const ty1 = (r.pos.y - l.p1.y) / dir.y; + const ty2 = (r.pos.y + r.height - l.p1.y) / dir.y; + + tmin = Math.max(tmin, Math.min(ty1, ty2)); + tmax = Math.min(tmax, Math.max(ty1, ty2)); + } + else { + if (l.p1.y < r.pos.y || l.p1.y > r.pos.y + r.height) { + return false; + } + } + + if (tmax >= tmin && tmax >= 0 && tmin <= 1) { + Vec2.addScaled(l.p1, dir, Math.max(tmin, 0), result.p1); + Vec2.addScaled(l.p1, dir, Math.min(tmax, 1), result.p2); + return true; + } + else { + return false; + } +} + export function testRectLine(r: Rect, l: Line): boolean { - /*if (testRectPoint(r, l.p1) || testRectPoint(r, l.p2)) { - return true - } - const pts = r.points() - return !!testLineLine(l, new Line(pts[0], pts[1])) - || !!testLineLine(l, new Line(pts[1], pts[2])) - || !!testLineLine(l, new Line(pts[2], pts[3])) - || !!testLineLine(l, new Line(pts[3], pts[0]))*/ const dir = l.p2.sub(l.p1); let tmin = Number.NEGATIVE_INFINITY, tmax = Number.POSITIVE_INFINITY; @@ -1266,6 +1647,11 @@ export function testRectLine(r: Rect, l: Line): boolean { tmin = Math.max(tmin, Math.min(tx1, tx2)); tmax = Math.min(tmax, Math.max(tx1, tx2)); } + else { + if (l.p1.x < r.pos.x || l.p1.x > r.pos.x + r.width) { + return false; + } + } if (dir.y != 0.0) { const ty1 = (r.pos.y - l.p1.y) / dir.y; @@ -1274,6 +1660,11 @@ export function testRectLine(r: Rect, l: Line): boolean { tmin = Math.max(tmin, Math.min(ty1, ty2)); tmax = Math.min(tmax, Math.max(ty1, ty2)); } + else { + if (l.p1.y < r.pos.y || l.p1.y > r.pos.y + r.height) { + return false; + } + } return tmax >= tmin && tmax >= 0 && tmin <= 1; } @@ -1320,6 +1711,76 @@ export function testLinePoint(l: Line, pt: Vec2): boolean { return t >= 0 && t <= 1; } +export function clipLineToCircle( + circle: Circle, + l: Line, + result: Line, +): boolean { + const v = l.p2.sub(l.p1); + const a = v.dot(v); + const centerToOrigin = l.p1.sub(circle.center); + const b = 2 * v.dot(centerToOrigin); + const c = centerToOrigin.dot(centerToOrigin) + - circle.radius * circle.radius; + // Calculate the discriminant of ax^2 + bx + c + const dis = b * b - 4 * a * c; + + // No root + if ((a <= Number.EPSILON) || (dis < 0)) { + return false; + } + // One possible root + else if (dis == 0) { + const t = -b / (2 * a); + if (t >= 0 && t <= 1) { + if (testCirclePoint(circle, l.p1)) { + Vec2.copy(l.p1, result.p1); + Vec2.addScaled(l.p1, v, t, result.p2); + } + else { + Vec2.addScaled(l.p1, v, t, result.p1); + Vec2.copy(l.p2, result.p2); + } + return true; + } + } + // Two possible roots + else { + const t1 = (-b + Math.sqrt(dis)) / (2 * a); + const t2 = (-b - Math.sqrt(dis)) / (2 * a); + const b1 = t1 >= 0 && t1 <= 1; + const b2 = t2 >= 0 && t2 <= 1; + if (b1 && b2) { + Vec2.addScaled(l.p1, v, t1, result.p1); + Vec2.addScaled(l.p1, v, t2, result.p2); + return true; + } + else if (b1 || b2) { + const t = b1 ? t1 : t2; + if (testCirclePoint(circle, l.p1)) { + Vec2.copy(l.p1, result.p1); + Vec2.addScaled(l.p1, v, t, result.p2); + } + else { + Vec2.addScaled(l.p1, v, t, result.p1); + Vec2.copy(l.p2, result.p2); + } + return true; + } + } + + // Check if line is completely within the circle + // We only need to check one point, since the line didn't cross the circle + if (testCirclePoint(circle, l.p1)) { + Vec2.copy(l.p1, result.p1); + Vec2.copy(l.p2, result.p2); + return true; + } + else { + return false; + } +} + export function testLineCircle(l: Line, circle: Circle): boolean { const v = l.p2.sub(l.p1); const a = v.dot(v); @@ -2085,8 +2546,8 @@ export class Point { constructor(pt: Vec2) { this.pt = pt.clone(); } - transform(m: Mat4): Point { - return new Point(m.multVec2(this.pt)); + transform(m: Mat23): Point { + return new Point(m.transformPoint(this.pt, vec2())); } bbox(): Rect { return new Rect(this.pt, 0, 0); @@ -2121,8 +2582,11 @@ export class Line { this.p1 = p1.clone(); this.p2 = p2.clone(); } - transform(m: Mat4): Line { - return new Line(m.multVec2(this.p1), m.multVec2(this.p2)); + transform(m: Mat23): Line { + return new Line( + m.transformPoint(this.p1, vec2()), + m.transformPoint(this.p2, vec2()), + ); } bbox(): Rect { return Rect.fromPoints(this.p1, this.p2); @@ -2177,8 +2641,10 @@ export class Rect { this.pos.add(0, this.height), ]; } - transform(m: Mat4): Polygon { - return new Polygon(this.points().map((pt) => m.multVec2(pt))); + transform(m: Mat23): Polygon { + return new Polygon( + this.points().map((pt) => m.transformPoint(pt, vec2())), + ); } bbox(): Rect { return this.clone(); @@ -2225,7 +2691,7 @@ export class Circle { this.center = center.clone(); this.radius = radius; } - transform(tr: Mat4): Ellipse { + transform(tr: Mat23): Ellipse { return new Ellipse(this.center, this.radius, this.radius).transform(tr); } bbox(): Rect { @@ -2308,13 +2774,13 @@ export class Ellipse { c * this.radiusY, ); } - transform(tr: Mat4): Ellipse { + transform(tr: Mat23): Ellipse { if (this.angle == 0 && tr.getRotation() == 0) { // No rotation, so we can just take the scale and translation return new Ellipse( - tr.multVec2(this.center), - tr.m[0] * this.radiusX, - tr.m[5] * this.radiusY, + tr.transformPoint(this.center, vec2()), + tr.a * this.radiusX, + tr.d * this.radiusY, ); } else { @@ -2329,7 +2795,7 @@ export class Ellipse { T = M.toMat2(); // Return the ellipse made from the transformed unit circle const ellipse = Ellipse.fromMat2(T); - ellipse.center = tr.multVec2(this.center); + ellipse.center = tr.transformPoint(this.center, vec2()); return ellipse; } } @@ -2415,8 +2881,8 @@ export class Polygon { } this.pts = pts; } - transform(m: Mat4): Polygon { - return new Polygon(this.pts.map((pt) => m.multVec2(pt))); + transform(m: Mat23): Polygon { + return new Polygon(this.pts.map((pt) => m.transformPoint(pt, vec2()))); } bbox(): Rect { const p1 = vec2(Number.MAX_VALUE); diff --git a/src/math/navigation.ts b/src/math/navigation.ts index 6807e66b..07e39b49 100644 --- a/src/math/navigation.ts +++ b/src/math/navigation.ts @@ -1,4 +1,4 @@ -import { BinaryHeap } from "../utils/"; +import { BinaryHeap } from "../utils"; import { Vec2 } from "./math"; export interface Graph { diff --git a/src/math/spatial/hashgrid.ts b/src/math/spatial/hashgrid.ts new file mode 100644 index 00000000..bdf03230 --- /dev/null +++ b/src/math/spatial/hashgrid.ts @@ -0,0 +1,77 @@ +import type { AreaComp } from "../../components/physics/area"; +import { DEF_HASH_GRID_SIZE } from "../../constants"; +import type { GameObj } from "../../types"; +import { calcTransform } from "../various"; + +class HashGrid { + grid: Record[]>> = {}; + cellSize: number; + objects: Array> = []; + + constructor(gopt: any) { + this.cellSize = gopt.hashGridSize || DEF_HASH_GRID_SIZE; + } + + add(obj: GameObj) { + this.objects.push(obj); + } + + remove(obj: GameObj) { + const index = this.objects.indexOf(obj); + if (index >= 0) { + this.objects.splice(index, 1); + } + } + + clear() { + this.objects = []; + } + + update() { + // Update edge data + for (const obj of this.objects) { + calcTransform(obj, obj.transform); + } + } + + /** + * Iterates all object pairs which potentially collide + */ + *[Symbol.iterator]() { + for (const obj of this.objects) { + const area = obj.worldArea(); + const bbox = area.bbox(); + + // Get spatial hash grid coverage + const xMin = Math.floor(bbox.pos.x / this.cellSize); + const yMin = Math.floor(bbox.pos.y / this.cellSize); + const xMax = Math.ceil((bbox.pos.x + bbox.width) / this.cellSize); + const yMax = Math.ceil((bbox.pos.y + bbox.height) / this.cellSize); + + // Cache objects that are already checked with this object + const checked = new Set(); + + // insert & check against all covered grids + for (let x = xMin; x <= xMax; x++) { + for (let y = yMin; y <= yMax; y++) { + if (!this.grid[x]) { + this.grid[x] = {}; + this.grid[x][y] = [obj]; + } + else if (!this.grid[x][y]) { + this.grid[x][y] = [obj]; + } + else { + const cell = this.grid[x][y]; + for (const other of cell) { + if (checked.has(other.id)) continue; + yield [obj, other]; + checked.add(other.id); + } + cell.push(obj); + } + } + } + } + } +} diff --git a/src/math/spatial/sweepandprune.ts b/src/math/spatial/sweepandprune.ts new file mode 100644 index 00000000..65c6cc7a --- /dev/null +++ b/src/math/spatial/sweepandprune.ts @@ -0,0 +1,103 @@ +import type { AreaComp } from "../../components"; +import type { GameObj } from "../../types"; +import { calcTransform } from "../various"; + +/** + * Left or right edge of an object's bbox + */ +class Edge { + obj: GameObj; + x: number; + isLeft: boolean; + + constructor(obj: GameObj, isLeft: boolean) { + this.obj = obj; + this.x = 0; + this.isLeft = isLeft; + } +} + +/** + * One dimensional sweep and prune + */ +export class SweepAndPrune { + edges: Array; + objects: Map, [Edge, Edge]>; + + constructor() { + this.edges = []; + this.objects = new Map, [Edge, Edge]>(); + } + + /** + * Add the object and its edges to the list + * @param obj The object to add + */ + add(obj: GameObj) { + const left = new Edge(obj, true); + const right = new Edge(obj, false); + this.edges.push(left); + this.edges.push(right); + this.objects.set(obj, [left, right]); + } + + /** + * Remove the object and its edges from the list + * @param obj The object to remove + */ + remove(obj: GameObj) { + const pair = this.objects.get(obj); + if (pair) { + this.edges.splice(this.edges.indexOf(pair[0]), 1); + this.edges.splice(this.edges.indexOf(pair[1]), 1); + this.objects.delete(obj); + } + } + + clear() { + this.edges = []; + this.objects.clear(); + } + + /** + * Update edges and sort + */ + update() { + // Update edge data + for (const [obj, edges] of this.objects.entries()) { + calcTransform(obj, obj.transform); + const bbox = obj.worldArea().bbox(); + edges[0].x = bbox.pos.x; + edges[1].x = bbox.pos.x + bbox.width; + } + // Insertion sort. This is slow the first time, but faster afterwards as the list is nearly sorted + for (let i = 1; i < this.edges.length; i++) { + for (let j = i - 1; j >= 0; j--) { + if (this.edges[j].x < this.edges[j + 1].x) break; + [this.edges[j], this.edges[j + 1]] = [ + this.edges[j + 1], + this.edges[j], + ]; + } + } + } + + /** + * Iterates all object pairs which potentially collide + */ + *[Symbol.iterator]() { + const touching = new Set>(); + + for (const edge of this.edges) { + if (edge.isLeft) { + for (const obj of touching) { + yield [obj, edge.obj]; + } + touching.add(edge.obj); + } + else { + touching.delete(edge.obj); + } + } + } +} diff --git a/src/math/various.ts b/src/math/various.ts index c3483a40..edc1db0c 100644 --- a/src/math/various.ts +++ b/src/math/various.ts @@ -1,21 +1,22 @@ import { height, width } from "../gfx"; import type { GameObj } from "../types"; -import { deg2rad, Mat4, Vec2, vec2 } from "./math"; +import { deg2rad, Mat23, Vec2, vec2 } from "./math"; -export function calcTransform(obj: GameObj): Mat4 { - const tr = new Mat4(); - if (obj.pos) tr.translate(obj.pos); - if (obj.scale) tr.scale(obj.scale); - if (obj.angle) tr.rotate(obj.angle); - return obj.parent ? tr.mult(obj.parent.transform) : tr; +export function calcTransform(obj: GameObj, tr: Mat23): Mat23 { + tr.setIdentity(); + if (obj.pos) tr.translateSelfV(obj.pos); + if (obj.scale) tr.scaleSelfV(obj.scale); + if (obj.angle) tr.rotateSelf(obj.angle); + if (obj.parent) { + tr.mulSelf(obj.parent.transform); + } + return tr; } // convert a screen space coordinate to webgl normalized device coordinate -export function screen2ndc(pt: Vec2): Vec2 { - return new Vec2( - pt.x / width() * 2 - 1, - -pt.y / height() * 2 + 1, - ); +export function screen2ndc(pt: Vec2, width: number, height: number, out: Vec2) { + out.x = pt.x / width * 2 - 1; + out.y = -pt.y / height * 2 + 1; } export function getArcPts( diff --git a/src/types.ts b/src/types.ts index 6a90606a..cc54c8af 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,7 @@ import type { BuoyancyEffectorComp, BuoyancyEffectorCompOpt, CircleComp, + CircleCompOpt, ColorComp, ConstantForceComp, ConstantForceCompOpt, @@ -42,12 +43,12 @@ import type { LifespanCompOpt, MaskComp, NamedComp, - NavigationComp, - NavigationCompOpt, OffScreenComp, OffScreenCompOpt, OpacityComp, OutlineComp, + PathfinderComp, + PathfinderCompOpt, PatrolComp, PatrolCompOpt, PlatformEffectorComp, @@ -117,6 +118,7 @@ import type { Circle, Ellipse, Line, + Mat23, Mat4, Point, Polygon, @@ -368,7 +370,18 @@ export interface KAPLAYCtx< * * @param a The angle to rotate by. Defaults to 0. * - * @group Components + * @example + * ```js + * let bean = add([ + * sprite("bean"), + * rotate(), + * ]) + * + * // bean will be upside down! + * bean.angle = 180 + * ``` + + * @group Components */ rotate(a?: number): RotateComp; /** @@ -393,6 +406,20 @@ export interface KAPLAYCtx< /** * Sets the opacity of a Game Object (0.0 - 1.0). * + * @example + * ```js + * const bean = add([ + * sprite("bean"), + * opacity(0.5) // Make bean 50% transparent + * ]) + * + * // Make bean invisible + * bean.opacity = 0 + * + * // Make bean fully visible + * bean.opacity = 1 + * ``` + * * @group Components */ opacity(o?: number): OpacityComp; @@ -510,7 +537,7 @@ export interface KAPLAYCtx< * * @group Components */ - circle(radius: number): CircleComp; + circle(radius: number, opt?: CircleCompOpt): CircleComp; /** * Attach and render an ellipse to a Game Object. * @@ -588,12 +615,50 @@ export interface KAPLAYCtx< /** * Determines the draw order for objects on the same layer. Object will be drawn on top if z value is bigger. * + * @example + * ```js + * const bean = add([ + * sprite("bean"), + * pos(100, 100), + * z(10), // Bean has a z value of 10 + * ]) + * + * // Mark has a z value of 20, so he will always be drawn on top of bean + * const mark = add([ + * sprite("mark"), + * pos(100, 100), + * z(20), + * ]) + * + * bean.z = 30 // Bean now has a higher z value, so it will be drawn on top of mark + * ``` + * * @group Components */ z(z: number): ZComp; /** * Determines the layer for objects. Object will be drawn on top if the layer index is higher. * + * @example + * ```js + * // Define layers + * layers(["background", "game", "foreground"], "game") + * + * const bean = add([ + * sprite("bean"), + * pos(100, 100), + * layer("background"), + * ]) + * + * // Mark is in a higher layer, so he will be drawn on top of bean + * const mark = add([ + * sprite("mark"), + * pos(100, 100), + * layer("game"), + * ]) + * + * bean.layer("foreground") // Bean is now in the foreground layer and will be drawn on top of mark + * * @group Components */ layer(name: string): LayerComp; @@ -615,6 +680,35 @@ export interface KAPLAYCtx< * * @param popt The options for the particles. * @param eopt The options for the emitter. + * + * @example + * ```js + * // beansplosion + * + * // create the emitter + * const emitter = add([ + * pos(center()), + * particles({ + * max: 100, + * speed: [75, 100], + * lifeTime: [0.75,1.0], + * angle: [0, 360], + * opacities: [1.0, 0.0], + * texture: getSprite("bean").tex, // texture of a sprite + * quads: getSprite("bean").frames, // frames of a sprite + * }, { + * direction: 0, + * spread: 360, + * }), + * ]) + * + * onUpdate(() => { + * emitter.emit(1) + * }) + * ``` + * + * @group Components + * @since v3001.0 */ particles(popt: ParticlesOpt, eopt: EmitterOpt): ParticlesComp; /** @@ -652,6 +746,23 @@ export interface KAPLAYCtx< * Applies a force on a colliding object in order to make it move along the collision tangent vector. * Good for conveyor belts. * + * @example + * ```js + * loadSprite("belt", "/sprites/jumpy.png") + * + * // conveyor belt + * add([ + * pos(center()), + * sprite("belt"), + * rotate(90), + * area(), + * body({ isStatic: true }), + * surfaceEffector({ + * speed: 50, + * }) + * ]) + * ``` + * * @since v3001.0 * @group Components */ @@ -666,7 +777,7 @@ export interface KAPLAYCtx< areaEffector(options: AreaEffectorCompOpt): AreaEffectorComp; /** * Applies a force on a colliding object directed towards this object's origin. - * Good to apply magnetic attractiong or repulsion. + * Good to apply magnetic attraction or repulsion. * * @since v3001.0 * @group Components @@ -743,11 +854,35 @@ export interface KAPLAYCtx< /** * Follow another game obj's position. * + * @example + * ```js + * const bean = add(...) + * + * add([ + * sprite("bag"), + * pos(), + * follow(bean) // Follow bean's position + * ]) + * ``` + * + * @example + * ```js + * const target = add(...) + * + * const mark = add([ + * sprite("mark"), + * pos(), + * follow(target, vec2(32, 32)) // Follow target's position with an offset + * ]) + * + * mark.follow.offset = vec2(64, 64) // Change the offset + * ``` + * * @group Components */ follow(obj: GameObj | null, offset?: Vec2): FollowComp; /** - * Custom shader. + * Custom shader to manipulate sprite. * * @group Components */ @@ -915,10 +1050,7 @@ export interface KAPLAYCtx< * * @group Components */ - state( - initialState: string, - stateList?: string[], - ): StateComp; + state(initialState: string, stateList?: string[]): StateComp; /** * state() with pre-defined transitions. * @@ -966,6 +1098,22 @@ export interface KAPLAYCtx< * @group Components */ mask(maskType?: Mask): MaskComp; + /** + * Specifies the FrameBuffer the object should be drawn on. + * + * @example + * ```js + * // Draw on another canvas + * let canvas = makeCanvas(width(), height()) + * + * let beanOnCanvas = add([ + * sprite("bean"), + * drawon(canvas.fb), + * ]) + * ``` + * + * @param canvas + */ drawon(canvas: FrameBuffer): Comp; /** * A tile on a tile map. @@ -984,6 +1132,22 @@ export interface KAPLAYCtx< /** * A component to animate properties. * + * @example + * ```js + * let movingBean = add([ + * sprite("bean"), + * pos(50, 150), + * anchor("center"), + * animate(), + * ]); + * + * // Moving right to left using ping-pong + * movingBean.animate("pos", [vec2(50, 150), vec2(150, 150)], { + * duration: 2, + * direction: "ping-pong", + * }); + * ``` + * * @since v3001.0 * @group Components */ @@ -1013,12 +1177,12 @@ export interface KAPLAYCtx< */ patrol(opts: PatrolCompOpt): PatrolComp; /** - * A navigator which can calculate waypoints to a goal. + * A navigator pathfinder which can calculate waypoints to a goal. * * @since v3001.0 * @group Components */ - navigation(opts: NavigationCompOpt): NavigationComp; + pathfinder(opts: PathfinderCompOpt): PathfinderComp; /** * @group Math */ @@ -1051,7 +1215,7 @@ export interface KAPLAYCtx< * ``` * @group Events */ - on( + on( event: Ev, tag: Tag, action: ( @@ -1159,28 +1323,28 @@ export interface KAPLAYCtx< * ``` * @group Events */ - onLoad(action: () => void): void; + onLoad(action: () => void): KEventController | undefined; /** * Register an event that runs every frame when assets are initially loading. Can be used to draw a custom loading screen. * * @since v3000.0 * @group Events */ - onLoading(action: (progress: number) => void): void; + onLoading(action: (progress: number) => void): KEventController; /** * Register a custom error handler. Can be used to draw a custom error screen. * * @since v3000.0 * @group Events */ - onError(action: (err: Error) => void): void; + onError(action: (err: Error) => void): KEventController; /** * Register an event that runs when the canvas resizes. * * @since v3000.0 * @group Events */ - onResize(action: () => void): void; + onResize(action: () => void): KEventController; /** * Cleanup function to run when quit() is called. * @@ -1194,14 +1358,14 @@ export interface KAPLAYCtx< * @since v3000.0 * @group Input */ - onGamepadConnect(action: (gamepad: KGamepad) => void): void; + onGamepadConnect(action: (gamepad: KGamepad) => void): KEventController; /** * Register an event that runs when a gamepad is disconnected. * * @since v3000.0 * @group Input */ - onGamepadDisconnect(action: (gamepad: KGamepad) => void): void; + onGamepadDisconnect(action: (gamepad: KGamepad) => void): KEventController; /** * Register an event that runs once when 2 game objs with certain tags collides (required to have area() component). * @@ -1294,6 +1458,14 @@ export interface KAPLAYCtx< /** * Register an event that runs every frame when game objs with certain tags are hovered (required to have area() component). * + * @example + * ```js + * // Rotate bean 90 degrees per second when hovered + * onHoverUpdate("bean", (bean) => { + * bean.angle += dt() * 90 + * }) + * ``` + * * @since v3000.0 * @group Events */ @@ -1363,7 +1535,7 @@ export interface KAPLAYCtx< */ onKeyPress(action: (key: Key) => void): KEventController; /** - * Register an event that runs when user presses certain kesy (also fires repeatedly when the keys are being held down). + * Register an event that runs when user presses certain keys (also fires repeatedly when the keys are being held down). * * @example * ```js @@ -1414,18 +1586,14 @@ export interface KAPLAYCtx< button: MouseButton | MouseButton[], action: (m: MouseButton) => void, ): KEventController; - onMouseDown( - action: (m: MouseButton) => void, - ): KEventController; + onMouseDown(action: (m: MouseButton) => void): KEventController; /** * Register an event that runs when user clicks mouse. * * @since v3001.0 * @group Input */ - onMousePress( - action: (m: MouseButton) => void, - ): KEventController; + onMousePress(action: (m: MouseButton) => void): KEventController; onMousePress( button: MouseButton | MouseButton[], action: (m: MouseButton) => void, @@ -1472,6 +1640,15 @@ export interface KAPLAYCtx< /** * Register an event that runs when mouse wheel scrolled. * + * @example + * ```js + * // Zoom camera on scroll + * onScroll((delta) => { + * const zoom = delta.y / 500 + * camScale(camScale().add(zoom)) + * }) + * ``` + * * @since v3000.0 * @group Input */ @@ -1762,7 +1939,7 @@ export interface KAPLAYCtx< */ loadSound( name: string | null, - src: string | ArrayBuffer, + src: string | ArrayBuffer | AudioBuffer, ): Asset; /** * Like loadSound(), but the audio is streamed and won't block loading. Use this for big audio files like background music. @@ -1773,10 +1950,7 @@ export interface KAPLAYCtx< * ``` * @group Assets */ - loadMusic( - name: string | null, - url: string, - ): void; + loadMusic(name: string | null, url: string): void; /** * Load a font (any format supported by the browser, e.g. ttf, otf, woff). * @@ -1795,7 +1969,7 @@ export interface KAPLAYCtx< opt?: LoadFontOpt, ): Asset; /** - * Load a bitmap font into asset manager, with name and resource url and infomation on the layout of the bitmap. + * Load a bitmap font into asset manager, with name and resource url and information on the layout of the bitmap. * * @since v3000.0 * @@ -2031,16 +2205,44 @@ export interface KAPLAYCtx< */ mouseDeltaPos(): Vec2; /** - * If certain keys are currently down. + * If any or certain key(s) are currently down. * * @example * ```js + * // Any key down + * + * let lastKeyTime = time() + * let triedToWakeUp = false + * + * onUpdate(() => { + * if (isKeyDown()) { + * lastKeyTime = time() + * triedToWakeUp = false + * return + * } + * + * if (triedToWakeUp || time() - lastKeyTime < 5) return + * + * debug.log("Wake up!") + * triedToWakeUp = true + * }) + * + * // Certain key down * // equivalent to the calling bean.move() in an onKeyDown("left") + * * onUpdate(() => { * if (isKeyDown("left")) { * bean.move(-SPEED, 0) * } * }) + * + * // Certain keys down + * + * let isMoving = false + * + * onUpdate(() => { + * isMoving = isKeyDown(["left", "right"]) + * }) * ``` * * @since v3001.0 @@ -2048,21 +2250,69 @@ export interface KAPLAYCtx< */ isKeyDown(k?: Key | Key[]): boolean; /** - * If certain keys are just pressed last frame. + * If any or certain key(s) are just pressed last frame. + * + * @example + * ```js + * onUpdate(() => { + * if (!isKeyPressed()) return // early return as no key was pressed + * + * if (isKeyPressed("space")) debug.log("Pressed the jump key") + * if (isKeyPressed(["left", "right"])) debug.log("Pressed any of the move keys") + * }) + * ``` * * @since v3001.0 * @group Input */ isKeyPressed(k?: Key | Key[]): boolean; /** - * If certain keys are just pressed last frame (also fires repeatedly when the keys are being held down). + * If any or certain key(s) are just pressed last frame (also fires repeatedly when the keys are being held down). + * + * @example + * ```js + * let heldKeys = new Set() + * + * onUpdate(() => { + * if (isKeyPressedRepeat("space")) { + * pressedOrHeld(["space"], 'the jump key') + * } else if (isKeyPressedRepeat(["left", "right"])) { + * pressedOrHeld(["left", "right"], 'any of the move keys') + * } else if (isKeyPressedRepeat()) { + * pressedOrHeld(["any"], 'any key') + * } + * }) + * + * onKeyRelease((key) => wait(0.1, () => { + * heldKeys.delete(key) + * heldKeys.delete("any") + * })) + * + * // log message if pressed only or held as well + * function pressedOrHeld(keys, string) { + * debug.log(`Pressed${keys.some(key => heldKeys.has(key)) ? ' and held' : ''} ${string}`) + * keys.forEach((key) => { + * if (key == "any" || isKeyDown(key)) heldKeys.add(key) + * }) + * } + * ``` * * @since v3001.0 * @group Input */ isKeyPressedRepeat(k?: Key | Key[]): boolean; /** - * If certain keys are just released last frame. + * If any or certain key(s) are just released last frame. + * + * @example + * ```js + * onUpdate(() => { + * if (!isKeyReleased()) return // early return as no key was released + * + * if (isKeyReleased("space")) debug.log("Released the jump key") + * if (isKeyReleased(["left", "right"])) debug.log("Released any of the move keys") + * }) + * ``` * * @since v3001.0 * @group Input @@ -2118,26 +2368,56 @@ export interface KAPLAYCtx< */ isGamepadButtonReleased(btn?: KGamepadButton | KGamepadButton[]): boolean; /** - * If certain binded buttons are just pressed last frame on any input (keyboard, gamepad). + * If any or certain bound button(s) are just pressed last frame on any input (keyboard, gamepad). + * + * @example + * ```js + * onUpdate(() => { + * if (!isButtonPressed()) return // early return as no button was pressed + * + * if (isButtonPressed("jump")) debug.log("Player jumped") + * if (isButtonPressed(["left", "right"])) debug.log("Player moved") + * }) + * ``` * * @since v3001.0 * @group Input */ - isButtonPressed(button: TButton | TButton[]): boolean; + isButtonPressed(button?: TButton | TButton[]): boolean; /** - * If certain binded buttons are currently held down on any input (keyboard, gamepad). + * If any or certain bound button(s) are currently held down on any input (keyboard, gamepad). + * + * @example + * ```js + * onUpdate(() => { + * if (!isButtonDown()) return // early return as no button is held down + * + * if (isButtonDown("jump")) debug.log("Player is jumping") + * if (isButtonDown(["left", "right"])) debug.log("Player is moving") + * }) + * ``` * * @since v3001.0 * @group Input */ - isButtonDown(button: TButton | TButton[]): boolean; + isButtonDown(button?: TButton | TButton[]): boolean; /** - * If certain binded buttons are just released last frame on any input (keyboard, gamepad). + * If any or certain bound button(s) are just released last frame on any input (keyboard, gamepad). + * + * @example + * ```js + * onUpdate(() => { + * if (!isButtonReleased()) return // early return as no button was released + * + * if (isButtonReleased("jump")) debug.log("Player stopped jumping") + * if (isButtonReleased(["left", "right"])) debug.log("Player stopped moving") + * }) + * ``` * * @since v3001.0 * @group Input */ - isButtonReleased(button: TButton | TButton[]): boolean; + isButtonReleased(button?: TButton | TButton[]): boolean; /** * Get a input binding from a button name. * @@ -2152,6 +2432,34 @@ export interface KAPLAYCtx< * @group Input */ setButton(button: string, def: ButtonBinding): void; + /** + * Press a button virtually. + * + * @since v3001.0 + * @group Input + * + * @example + * ```js + * // press "jump" button + * pressButton("jump"); // triggers onButtonPress, starts onButtonDown + * releaseButton("jump"); // triggers onButtonRelease, stops onButtonDown + * ``` + */ + pressButton(button: TButton): void; + /** + * Release a button virtually. + * + * @since v3001.0 + * @group Input + * + * @example + * ```js + * // press "jump" button + * pressButton("jump"); // triggers onButtonPress, starts onButtonDown + * releaseButton("jump"); // triggers onButtonRelease, stops onButtonDown + * ``` + */ + releaseButton(button: TButton): void; /** * Get stick axis values from a gamepad. * @@ -2224,6 +2532,14 @@ export interface KAPLAYCtx< /** * Flash the camera. * + * @example + * ```js + * onClick(() => { + * // flashed + * camFlash(WHITE, 0.5) + * }) + * ``` + * * @group Info */ camFlash(flashColor: Color, fadeOutTime: number): TimerController; @@ -2232,7 +2548,7 @@ export interface KAPLAYCtx< * * @group Info */ - camTransform(): Mat4; + camTransform(): Mat23; /** * Transform a point from world position (relative to the root) to screen position (relative to the screen). * @since v3001.0 @@ -2707,25 +3023,13 @@ export interface KAPLAYCtx< * * @group Math */ - map( - v: number, - l1: number, - h1: number, - l2: number, - h2: number, - ): number; + map(v: number, l1: number, h1: number, l2: number, h2: number): number; /** * Map a value from one range to another range, and clamp to the dest range. * * @group Math */ - mapc( - v: number, - l1: number, - h1: number, - l2: number, - h2: number, - ): number; + mapc(v: number, l1: number, h1: number, l2: number, h2: number): number; /** * Interpolate between 2 values (Optionally takes a custom periodic function, which default to Math.sin). * @@ -2988,6 +3292,16 @@ export interface KAPLAYCtx< * @group Math */ testCirclePolygon(c: Circle, p: Polygon): boolean; + /** + * @since v4000.0 + * @group Math + */ + clipLineToRect(r: Rect, l: Line, result: Line): boolean; + /** + * @since v4000.0 + * @group Math + */ + clipLineToCircle(c: Circle, l: Line, result: Line): boolean; /** * @since v4000.0 * @group Math @@ -3049,6 +3363,10 @@ export interface KAPLAYCtx< * @group Math */ Mat4: typeof Mat4; + /** + * @group Math + */ + Mat23: typeof Mat23; /** * @group Math */ @@ -3074,7 +3392,7 @@ export interface KAPLAYCtx< * * @group Scene */ - layers(layernames: string[], defaultLayer: string): void; + layers(layers: string[], defaultLayer: string): void; /** * Construct a level based on symbols. * @@ -3398,17 +3716,13 @@ export interface KAPLAYCtx< * * @group Draw */ - pushTranslate(x: number, y: number): void; - pushTranslate(p: Vec2): void; - pushTranslate(...args: Vec2Args | [undefined]): void; + pushTranslate(t?: Vec2): void; /** * Scale all subsequent draws. * * @group Draw */ - pushScale(x: number, y: number): void; - pushScale(s: Vec2 | number): void; - pushScale(...args: Vec2Args | [undefined] | [undefined, undefined]): void; + pushScale(s?: Vec2): void; /** * Rotate all subsequent draws. * @@ -3421,7 +3735,7 @@ export interface KAPLAYCtx< * @since v3000.0 * @group Draw */ - pushMatrix(mat?: Mat4): void; + pushMatrix(mat?: Mat23): void; /** * Apply a post process effect from a shader name. * @@ -3494,7 +3808,7 @@ export interface KAPLAYCtx< */ plug>(plugin: KAPLAYPlugin): KAPLAYCtx & T; /** - * Take a screenshot and get the dataurl of the image. + * Take a screenshot and get the data url of the image. * * @returns The dataURL of the image. * @group Data @@ -3665,15 +3979,17 @@ export interface KAPLAYCtx< export type Tag = string; -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends - ((k: infer I) => void) ? I : never; +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void ? I + : never; type Defined = T extends any ? Pick : never; type Expand = T extends infer U ? { [K in keyof U]: U[K] } : never; export type MergeObj = Expand>>; /** - * A type to merge the components of a game object, omiting the default component properties. + * A type to merge the components of a game object, omitting the default component properties. * * @group Component Types */ @@ -3772,19 +4088,14 @@ export type Key = | "down" | "shift" ) - | string & {}; + | (string & {}); /** * A mouse button. * * @group Input */ -export type MouseButton = - | "left" - | "right" - | "middle" - | "back" - | "forward"; +export type MouseButton = "left" | "right" | "middle" | "back" | "forward"; /** * A gamepad button. @@ -3826,7 +4137,7 @@ export type GamepadDef = { sticks: Partial>; }; -/** A KAPLAY's gamepad */ +/** A KAPLAY gamepad */ export type KGamepad = { /** The order of the gamepad in the gamepad list. */ index: number; @@ -4169,7 +4480,7 @@ export interface GameObjRaw { * * @since v3000.0 */ - transform: Mat4; + transform: Mat23; /** * If draw the game obj (run "draw" event or not). */ @@ -4222,7 +4533,7 @@ export type GameObj = GameObjRaw & MergeComps; */ export type GetOpt = { /** - * Recursively get all children and their descendents. + * Recursively get all children and their descendants. */ recursive?: boolean; /** @@ -4358,6 +4669,13 @@ export interface Vertex { opacity: number; } +export interface Attributes { + pos: number[]; + uv: number[]; + color: number[]; + opacity: number[]; +} + /** * Texture scaling filter. "nearest" is mainly for sharp pixelated scaling, "linear" means linear interpolation. */ @@ -4369,7 +4687,7 @@ export type TexWrap = "repeat" | "clampToEdge"; */ export interface RenderProps { pos?: Vec2; - scale?: Vec2 | number; + scale?: Vec2; angle?: number; color?: Color; opacity?: number; @@ -4513,7 +4831,7 @@ export type DrawPolygonOpt = RenderProps & { export interface Outline { /** - * The width, or thinkness of the line. + * The width, or thickness of the line. */ width?: number; /** @@ -4605,18 +4923,12 @@ export type Anchor = /** * @group Math */ -export type LerpValue = - | number - | Vec2 - | Color; +export type LerpValue = number | Vec2 | Color; /** * @group Math */ -export type RNGValue = - | number - | Vec2 - | Color; +export type RNGValue = number | Vec2 | Color; /** * @group Components @@ -4741,13 +5053,7 @@ export interface Collision { /** * @group Draw */ -export type Shape = - | Rect - | Line - | Point - | Circle - | Ellipse - | Polygon; +export type Shape = Rect | Line | Point | Circle | Ellipse | Polygon; /** * @group Debug @@ -4818,11 +5124,7 @@ export type Mask = "intersect" | "subtract"; /** * @group Math */ -export type Edge = - | "left" - | "right" - | "top" - | "bottom"; +export type Edge = "left" | "right" | "top" | "bottom"; /** * @group Math diff --git a/src/utils/events.ts b/src/utils/events.ts index f36b1b63..fed2dcd4 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -35,7 +35,7 @@ export class KEventController { /** If the event is paused */ paused: boolean = false; /** Cancel the event */ - readonly cancel: () => void; + cancel: () => void; constructor(cancel: () => void) { this.cancel = cancel; @@ -51,6 +51,16 @@ export class KEventController { ev.paused = false; return ev; } + static replace(oldEv: KEventController, newEv: KEventController) { + oldEv.cancel = () => newEv.cancel(); + newEv.paused = oldEv.paused; + Object.defineProperty(oldEv, "paused", { + get: () => newEv.paused, + set: (p: boolean) => newEv.paused = p, + }); + + return oldEv; + } } export class KEvent { diff --git a/tsconfig.dts.json b/tsconfig.dts.json new file mode 100644 index 00000000..e8fef5ac --- /dev/null +++ b/tsconfig.dts.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "skipLibCheck": true, + "resolveJsonModule": true, + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "declarationDir": "dist/declaration", + "strict": true + }, + "include": [ + "src/**/*" + ] +} diff --git a/tsconfig.json b/tsconfig.json index cb4e17b0..e720729d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "esModuleInterop": true, "target": "ESNext", + "module": "ESNext", "moduleResolution": "Node", "noImplicitThis": true, "skipLibCheck": true,