commit 842bb7cbd83327b25827d3acf49987ffca413c37 Author: Index Date: Sat Mar 15 09:51:20 2025 -0500 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e8233f2 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +JWT_TOKEN= \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9ee9be6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Auto detect text files and perform LF normalization +* text=auto +public/** -linguist-vendored +public/* -linguist-vendored +public/** linguist-vendored +public/* linguist-vendored \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d09074 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.DS_Store +Thumbs.db +accounts.json +/node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b391782 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM denoland/deno:2.1.5 + +WORKDIR /app + +COPY deno.json . + +RUN deno install + +COPY . . +RUN deno cache main.ts + +ARG PORT=3257 +EXPOSE $PORT + +CMD ["task", "start"] \ No newline at end of file diff --git a/Events.md b/Events.md new file mode 100644 index 0000000..50ec3e7 --- /dev/null +++ b/Events.md @@ -0,0 +1,189 @@ +# Event Recreation Progress + +A checklist for each party of which rooms are finished. Not all parties will be possible due to them not being archived sadly. This list ignores tedious animations which can be done at a later date. + +## Default (no party) + +- [x] Tavern +- [x] Crash Site +- [x] Cellar +- [x] Port +- [ ] Jungle + +## 2019 + +### Easter + +- [x] Tavern + +### Halloween + +- [ ] Tavern +- [ ] **Halloween Maze Entrance** +- [ ] **Halloween Maze** +- [ ] **Halloween Maze End** + +## Battle Bears + +This event had no significant changes. The Crash Site room became a default room, and is classified as that. + +### Christmas + +- [x] Tavern +- [x] **Snowman Village** +- [ ] Forest + +## 2020 + +### Saint Patrick's Day + +- [x] Tavern +- [x] **Leprechaun Village** +- [ ] Forest + +### Easter + +- [x] Tavern +- [x] Cellar + +### Graduation + +- [ ] Tavern +- [x] Cellar +- [ ] Port + +### Summer + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Shack + +### Space + +- [x] Tavern +- [x] Port +- [x] Shack +- [x] **Solar System** *(The background and foreground that were archived are corrupted)* + +### Halloween + +- [x] Tavern +- [x] Cellar +- [x] Port +- [ ] Jungle +- [x] **Halloween Maze Entrance** +- [x] **Halloween Maze** +- [x] **Halloween Maze End** + +### Club Penguin Celebration + +- [x] Port (practically the same as `Halloween 2020`'s Port, just with a party hat on the outside of the `Shack`) +- [x] Shack + +### CritterCon + +- [ ] Port +- [ ] **Critter Con Hall** + +### Holidays + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Shack +- [ ] Jungle +- [ ] **Cliff** +- [ ] **Snowman Village** +- [ ] **Forest** + +## 2021 + +### Graduation + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Jungle + +### Sports + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Shack +- [ ] Jungle + +### New Years + +- [ ] Port +*The wiki only includes a screenshot of the Port, and no other rooms.* + +### 2nd Anniversary + +- [ ] Tavern +- [ ] Cellar +- [ ] Port + +### Saint Patrick's Day + +- [ ] Tavern +- [ ] Port +- [ ] Jungle +- [ ] **Lava Lair** + +### April Fools' Day + +- [x] Tavern +- [x] Cellar +- [x] Port +- [x] Shack +- [x] Jungle +- [x] **Box Realm (work in progress)** *(the background is the only file found so far)* + +### Easter + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Shack + +### Sand Sculpture + +- [ ] Tavern +- [ ] Cellar +- [ ] Bridge +- [ ] Port +- [ ] Shack +- [ ] Jungle + +### Halloween + +- [x] Tavern +- [ ] Crash Site +- [x] Cellar +- [ ] Port +- [x] Jungle +- [ ] **Halloween Maze Entrance** +- [ ] **Halloween Maze** +- [ ] **Halloween Maze End** +- [x] **Neighborhood** +- [x] **Crypt** + +### Club Penguin Celebration + +- [x] Port (practically the same as `Halloween 2021`'s Port, just with a party hat on the outside of the `Shack`) +- [x] Shack + +### CritterCon + +- [ ] Port +- [ ] Critter Con Hall + +### Holidays + +- [ ] Tavern +- [ ] Cellar +- [ ] Port +- [ ] Jungle +- [ ] Shack \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..49fbd1d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Box Critters Recritted + +Reopening the dusty box of the world of Box Critters! This is a Typescript server emulator using the Bun & Deno frameworks. + +## Party Switcher + +A custom party switcher has been implemented, you can change the party on the log-in page, or using the `/party [ID]` command in-game. For a breakdown of party room recreation progress, go [here](Events.md). + +## Development + +> Installation +```bash +deno install +``` + +> Serving +```bash +deno run start +> Listening on http://localhost:3257/ +``` + +## APIs + +The game has 4 APIs: + +### (GET) `/api/server/players` + +This API returns information on the player(s) in-game, if any. + +### (GET) `/api/server/rooms` + +This API returns almost-identical information as the `/api/client/rooms` API, however it returns information on all hashes of all rooms with no required party ID URL parameter. + +### (POST) `/api/client/login` + +This API takes in all the information provided by the user on log-in and generates a JWT for that session. + +### (GET) `/api/client/rooms?partyId=` + +This API returns information on the parties the game supports, and depending on the party ID provided in the URL, information for each room that party changes in some way. If the party does not change the room in any way, the default version of that room will be returned. All the data is gathered by the server reading the `/public/media/rooms/` directory of the game - and cached for future requests. \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..f6749a3 Binary files /dev/null and b/bun.lockb differ diff --git a/chalk.d.ts b/chalk.d.ts new file mode 100644 index 0000000..d0ce642 --- /dev/null +++ b/chalk.d.ts @@ -0,0 +1,10 @@ +/* + Typescript doesn't seem to recognize the functions of the chalk_deno module, so some are specified here to avoid type warnings. +*/ + +declare module "https://deno.land/x/chalk_deno@v4.1.1-deno/source/index.js" { + export function red(text: string): string; + export function green(text: string): string; + export function blue(text: string): string; + export function gray(text: string): string; +} diff --git a/constants/items.ts b/constants/items.ts new file mode 100644 index 0000000..23bcee3 --- /dev/null +++ b/constants/items.ts @@ -0,0 +1,140 @@ +export const shop = { + lastItem: { itemId: "beard2_brown", cost: 20 }, + freeItem: { itemId: "goggles_pink", cost: 0}, + nextItem: { itemId: "scout_uniform", cost: 0}, + collection: [ + { itemId: "santa_hat", cost: 100 } + ] +} + +export const codes = { + rocketsnail: ["viking", "rocket_red"], + andybulletin: "propeller", + cute: "toque_pink", + oommgames: "space_red", + boxcritters3d: ["3d_white", "3d_black"], + goodnight: "sleeping", + madeincanada: "toque_white", + thekeeper: "party_green", + bunnyhug: "hoodie_blue", + greenplumber: "ballcap_green", + duckhunter: "float_pink", + piratepack: "pirate_patch", + pickle: "pickle", + oscarproductions: "guitar_blue", + livestream: "headphones_black", + creative: "headphones_black", + fun: "propeller_pink", + marco: "keytar_red", + snowball: "hoodie_white", + critbits: "ballcap_pink", + esporte: "kit_yellow", + squeeze: "hoodie_orange", + imagination: "box_brown", + sparkle: "propeller_silver", + adventure: "snorkel_blue", + tamago: "sun_square", + redcross: "australian_hat", + discordcritterspt: "headphones_green", + scarletraven: "wings_black", + boxcritterswiki: "paperhat_colour", + wikicritters: "brain_green", + boxcrittersguild: "cardboard_sword_silver", + staysafe: "doctor_mask_blue", + glitter: "pirate_capt_pink" +} + +export const throwback = [ + "goggles_black", + "pot", + "sombrero_yellow", + "cone", + "toque_purple", + "sun_orange", + "pirate_capt_black", + "lifejacket_red", + "ballcap_black", + "super_cape_red", + "tshirt_white", + "lei_red", + "ballcap_blue", + "viking_blue", + "overalls_orange", + "bunny_blue", + "messenger_brown", + "plaid_black", + "tinfoil_hat", + "monk", + "pirate_hat_black", + "hawaii_orange", + "space_black", + "traffic_cone", + "grass_yellow", + "ushanka", + "bandana_purple", + "ski_suit_blue", + "hotdog", + "space_blue", + "sweater_orange", + "tactical_headset", + "ballcap_yellow", + "ringmaster_suit_red", + "flight_helmet_red", + "rainhat_yellow", + "raincoat_yellow", + "ballcap_red", + "ballerina_pink", + "hawaii_blue", + "stripe_red_white", + "grass_green", + "plaid_blue", + "toque_orange", + "school_pack_orange", + "pirate_crew_blue", + "ringmaster_hat_black", + "super_mask_black", + "pirate_hat_pink", + "rainhat_red", + "tophat_black", + "scarf_red", + "skeleton_body", + "dracula_cloak", + "pumpkin", + "hotdog", + "beard3_black", + "scarf_purple", + "puffy_red", + "blockhead", + "hoodie_purple", + "winter_dress_red", + "bulb_blue", + "tacky_red", + "pirate_bandana_red" +] + +export const eventCodes = { + christmas2019: { + jinglebells: "elf_hat_green", + winter: "winter_dress", + joy: "bulb_yellow", + rudolph: "reindeer_head", + elf: "elf_suit_green", + cheer: "bulb_green", + glow: "bulb_red", + rednose: "reindeer_body", + blizzard: "wizard_blizzard", + family: "ornament_red", + shine: "angel_halo", + peace: "angel_wings", + jolly: "santa_beard", + merry: "santa_hat", + christmas: "santa_suit", + boxingday: "onsie_plaid_red", + warm: "sleeping_red", + tacky: "tacky_green", + snow: "goggles_white", + beautiful: "winter_dress_red", + newyear: "tuxedo_black", + "2020": "tophat_black" + } +} \ No newline at end of file diff --git a/constants/parties.json b/constants/parties.json new file mode 100644 index 0000000..65be278 --- /dev/null +++ b/constants/parties.json @@ -0,0 +1,14 @@ +[ + "default", + "easter2019", + "christmas2019", + "lucky2020", + "easter2020", + "space", + "grad2020", + "summer2020", + "halloween2019", + "halloween2020", + "fools2021", + "halloween2021" +] \ No newline at end of file diff --git a/constants/world.ts b/constants/world.ts new file mode 100644 index 0000000..1678588 --- /dev/null +++ b/constants/world.ts @@ -0,0 +1,90 @@ +import { PlayerCrumb, Room } from "../types.ts" +import { indexRoomData } from "../utils.ts"; + +export const rooms: Record> = await indexRoomData() +export const spawnRoom = "tavern"; + +export const players: Record = { + "0": { + "i": "0", + "n": "Huggable", + "c": "huggable", + "x": 1670, + "y": 323, + "r": 180, + "g": [], + "m": "", + "e": "", + "_roomId": "crash_site" + } +} +export const queue: Array = [] + +export const roomExits = { + "cellar->tavern": { x: 360, y: 410, r: 0 }, + "crash_site->cellar": { x: 615, y: 400, r: 0 }, + "shack->port": { x: 550, y: 235, r: 0 }, + "jungle->port": { x: 650, y: 230, r: 0 }, + "snowman_village->tavern": { x: 563, y: 368, r: 0 } +} + +// deno-lint-ignore no-explicit-any +export const npcs: { [key: string]: any } = { + snowman_village: [ + { + "i": "NPC0", + "n": "Snow Girl", + "c": "snowgirl", + "x": 1289, + "y": 228, + "r": 180, + "g": [], + "m": "", + "e": "" + }, + { + "i": "NPC1", + "n": "Snow Patrol", + "c": "snow_patrol", + "x": 1644, + "y": 221, + "r": 180, + "g": [], + "m": "", + "e": "" + }, + { + "i": "NPC2", + "n": "Snow Greeter", + "c": "snow_greeter", + "x": 443, + "y": 317, + "r": 180, + "g": [], + "m": "", + "e": "" + }, + { + "i": "NPC3", + "n": "Snow Grandma", + "c": "snowgrandma", + "x": 1938, + "y": 251, + "r": 180, + "g": [], + "m": "", + "e": "" + }, + { + "i": "NPC4", + "n": "Snow Keeper", + "c": "snowkeeper", + "x": 893, + "y": 216, + "r": 180, + "g": [], + "m": "", + "e": "" + } + ] +} \ No newline at end of file diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..b1a9c68 --- /dev/null +++ b/deno.json @@ -0,0 +1,12 @@ +{ + "imports": { + "hono": "jsr:@hono/hono@^4.6.11" + }, + "tasks": { + "start": "deno run --watch --allow-net --allow-read --allow-env --env-file=.env --allow-write main.ts" + }, + "compilerOptions": { + "jsx": "precompile", + "jsxImportSource": "hono/jsx" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..f932e7d --- /dev/null +++ b/deno.lock @@ -0,0 +1,647 @@ +{ + "version": "4", + "specifiers": { + "jsr:@hono/hono@^4.6.11": "4.6.11", + "jsr:@std/encoding@*": "1.0.5", + "npm:fs@^0.0.1-security": "0.0.1-security", + "npm:http@^0.0.1-security": "0.0.1-security", + "npm:nodemon@^3.1.7": "3.1.9", + "npm:path@~0.12.7": "0.12.7", + "npm:socket.io@^4.8.1": "4.8.1", + "npm:zod@^3.24.1": "3.24.2" + }, + "jsr": { + "@hono/hono@4.6.11": { + "integrity": "07399d911f09e94b7dc1e0e0a0577d35fe66578af20163d513958364c4e9e702" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + } + }, + "npm": { + "@socket.io/component-emitter@3.1.2": { + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "@types/cors@2.8.17": { + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": [ + "@types/node@22.12.0" + ] + }, + "@types/node@22.12.0": { + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dependencies": [ + "undici-types" + ] + }, + "@types/node@22.13.8": { + "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", + "dependencies": [ + "undici-types" + ] + }, + "accepts@1.3.8": { + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": [ + "mime-types", + "negotiator" + ] + }, + "anymatch@3.1.3": { + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": [ + "normalize-path", + "picomatch" + ] + }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64id@2.0.0": { + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "binary-extensions@2.3.0": { + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" + }, + "brace-expansion@1.1.11": { + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "chokidar@3.6.0": { + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": [ + "anymatch", + "braces", + "fsevents", + "glob-parent", + "is-binary-path", + "is-glob", + "normalize-path", + "readdirp" + ] + }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cookie@0.7.2": { + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + }, + "cors@2.8.5": { + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": [ + "object-assign", + "vary" + ] + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms" + ] + }, + "debug@4.4.0": { + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": [ + "ms" + ] + }, + "engine.io-parser@5.2.3": { + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "engine.io@6.6.4": { + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": [ + "@types/cors", + "@types/node@22.13.8", + "accepts", + "base64id", + "cookie", + "cors", + "debug@4.3.7", + "engine.io-parser", + "ws" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "fs@0.0.1-security": { + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "has-flag@3.0.0": { + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "http@0.0.1-security": { + "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" + }, + "ignore-by-default@1.0.1": { + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "inherits@2.0.3": { + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "is-binary-path@2.1.0": { + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": [ + "binary-extensions" + ] + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "mime-db@1.52.0": { + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types@2.1.35": { + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": [ + "mime-db" + ] + }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion" + ] + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "negotiator@0.6.3": { + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "nodemon@3.1.9": { + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dependencies": [ + "chokidar", + "debug@4.4.0", + "ignore-by-default", + "minimatch", + "pstree.remy", + "semver", + "simple-update-notifier", + "supports-color", + "touch", + "undefsafe" + ] + }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "object-assign@4.1.1": { + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "path@0.12.7": { + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dependencies": [ + "process", + "util" + ] + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "process@0.11.10": { + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "pstree.remy@1.1.8": { + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "readdirp@3.6.0": { + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": [ + "picomatch" + ] + }, + "semver@7.7.1": { + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + }, + "simple-update-notifier@2.0.0": { + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": [ + "semver" + ] + }, + "socket.io-adapter@2.5.5": { + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": [ + "debug@4.3.7", + "ws" + ] + }, + "socket.io-parser@4.2.4": { + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7" + ] + }, + "socket.io@4.8.1": { + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": [ + "accepts", + "base64id", + "cors", + "debug@4.3.7", + "engine.io", + "socket.io-adapter", + "socket.io-parser" + ] + }, + "supports-color@5.5.0": { + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": [ + "has-flag" + ] + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, + "touch@3.1.1": { + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==" + }, + "undefsafe@2.0.5": { + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "undici-types@6.20.0": { + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "util@0.10.4": { + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": [ + "inherits" + ] + }, + "vary@1.1.2": { + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "ws@8.17.1": { + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + }, + "zod@3.24.2": { + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==" + } + }, + "redirects": { + "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts" + }, + "remote": { + "https://deno.land/std@0.150.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.150.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06", + "https://deno.land/std@0.150.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.150.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620", + "https://deno.land/std@0.150.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a", + "https://deno.land/std@0.150.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c", + "https://deno.land/std@0.150.0/async/mod.ts": "9852cd8ed897ab2d41a8fbee611d574e97898327db5c19d9d58e41126473f02c", + "https://deno.land/std@0.150.0/async/mux_async_iterator.ts": "5b4aca6781ad0f2e19ccdf1d1a1c092ccd3e00d52050d9c27c772658c8367256", + "https://deno.land/std@0.150.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", + "https://deno.land/std@0.150.0/async/tee.ts": "bcfae0017ebb718cf4eef9e2420e8675d91cb1bcc0ed9b668681af6e6caad846", + "https://deno.land/std@0.150.0/bytes/bytes_list.ts": "aba5e2369e77d426b10af1de0dcc4531acecec27f9b9056f4f7bfbf8ac147ab4", + "https://deno.land/std@0.150.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a", + "https://deno.land/std@0.150.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.150.0/fmt/colors.ts": "6f9340b7fb8cc25a993a99e5efc56fe81bb5af284ff412129dd06df06f53c0b4", + "https://deno.land/std@0.150.0/fs/exists.ts": "cb734d872f8554ea40b8bff77ad33d4143c1187eac621a55bf37781a43c56f6d", + "https://deno.land/std@0.150.0/http/server.ts": "0b0a9f3abfcfecead944b31ee9098a0c11a59b0495bf873ee200eb80e7441483", + "https://deno.land/std@0.150.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.150.0/log/handlers.ts": "b88c24df61eaeee8581dbef3622f21aebfd061cd2fda49affc1711c0e54d57da", + "https://deno.land/std@0.150.0/log/levels.ts": "82c965b90f763b5313e7595d4ba78d5095a13646d18430ebaf547526131604d1", + "https://deno.land/std@0.150.0/log/logger.ts": "4d25581bc02dfbe3ad7e8bb480e1f221793a85be5e056185a0cea134f7a7fdf4", + "https://deno.land/std@0.150.0/log/mod.ts": "65d2702785714b8d41061426b5c279f11b3dcbc716f3eb5384372a430af63961", + "https://deno.land/std@0.150.0/testing/_diff.ts": "029a00560b0d534bc0046f1bce4bd36b3b41ada3f2a3178c85686eb2ff5f1413", + "https://deno.land/std@0.150.0/testing/_format.ts": "0d8dc79eab15b67cdc532826213bbe05bccfd276ca473a50a3fc7bbfb7260642", + "https://deno.land/std@0.150.0/testing/_test_suite.ts": "ad453767aeb8c300878a6b7920e20370f4ce92a7b6c8e8a5d1ac2b7c14a09acb", + "https://deno.land/std@0.150.0/testing/asserts.ts": "0ee58a557ac764e762c62bb21f00e7d897e3919e71be38b2d574fb441d721005", + "https://deno.land/std@0.150.0/testing/bdd.ts": "182bb823e09bd75b76063ecf50722870101b7cfadf97a09fa29127279dc21128", + "https://deno.land/std@0.158.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.158.0/async/deferred.ts": "c01de44b9192359cebd3fe93273fcebf9e95110bf3360023917da9a2d1489fae", + "https://deno.land/std@0.158.0/async/delay.ts": "0419dfc993752849692d1f9647edf13407c7facc3509b099381be99ffbc9d699", + "https://deno.land/std@0.158.0/bytes/bytes_list.ts": "aba5e2369e77d426b10af1de0dcc4531acecec27f9b9056f4f7bfbf8ac147ab4", + "https://deno.land/std@0.158.0/bytes/equals.ts": "3c3558c3ae85526f84510aa2b48ab2ad7bdd899e2e0f5b7a8ffc85acb3a6043a", + "https://deno.land/std@0.158.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.158.0/io/buffer.ts": "fae02290f52301c4e0188670e730cd902f9307fb732d79c4aa14ebdc82497289", + "https://deno.land/std@0.162.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06", + "https://deno.land/std@0.162.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.162.0/async/debounce.ts": "dc8b92d4a4fe7eac32c924f2b8d3e62112530db70cadce27042689d82970b350", + "https://deno.land/std@0.162.0/async/deferred.ts": "d8fb253ffde2a056e4889ef7e90f3928f28be9f9294b6505773d33f136aab4e6", + "https://deno.land/std@0.162.0/async/delay.ts": "0419dfc993752849692d1f9647edf13407c7facc3509b099381be99ffbc9d699", + "https://deno.land/std@0.162.0/async/mod.ts": "dd0a8ed4f3984ffabe2fcca7c9f466b7932d57b1864ffee148a5d5388316db6b", + "https://deno.land/std@0.162.0/async/mux_async_iterator.ts": "3447b28a2a582224a3d4d3596bccbba6e85040da3b97ed64012f7decce98d093", + "https://deno.land/std@0.162.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", + "https://deno.land/std@0.162.0/async/tee.ts": "9af3a3e7612af75861308b52249e167f5ebc3dcfc8a1a4d45462d96606ee2b70", + "https://deno.land/std@0.162.0/http/server.ts": "e99c1bee8a3f6571ee4cdeb2966efad465b8f6fe62bec1bdb59c1f007cc4d155", + "https://deno.land/std@0.177.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.177.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.177.0/bytes/index_of_needle.ts": "65c939607df609374c4415598fa4dad04a2f14c4d98cd15775216f0aaf597f24", + "https://deno.land/std@0.177.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1", + "https://deno.land/std@0.177.0/encoding/base64url.ts": "3f1178f6446834457b16bfde8b559c1cd3481727fe384d3385e4a9995dc2d851", + "https://deno.land/std@0.177.0/node/_core.ts": "9a58c0ef98ee77e9b8fcc405511d1b37a003a705eb6a9b6e95f75434d8009adc", + "https://deno.land/std@0.177.0/node/_utils.ts": "7fd55872a0cf9275e3c080a60e2fa6d45b8de9e956ebcde9053e72a344185884", + "https://deno.land/std@0.177.0/node/buffer.ts": "85617be2063eccaf177dbb84c7580d1e32023724ed14bd9df4e453b152a26167", + "https://deno.land/std@0.177.0/node/internal/buffer.mjs": "e92303a3cc6d9aaabcd270a937ad9319825d9ba08cb332650944df4562029b27", + "https://deno.land/std@0.177.0/node/internal/crypto/_keys.ts": "8f3c3b5a141aa0331a53c205e9338655f1b3b307a08085fd6ff6dda6f7c4190b", + "https://deno.land/std@0.177.0/node/internal/crypto/constants.ts": "544d605703053218499b08214f2e25cf4310651d535b7ab995891c4b7a217693", + "https://deno.land/std@0.177.0/node/internal/error_codes.ts": "8495e33f448a484518d76fa3d41d34fc20fe03c14b30130ad8e936b0035d4b8b", + "https://deno.land/std@0.177.0/node/internal/errors.ts": "1c699b8a3cb93174f697a348c004b1c6d576b66688eac8a48ebb78e65c720aae", + "https://deno.land/std@0.177.0/node/internal/hide_stack_frames.ts": "9dd1bad0a6e62a1042ce3a51eb1b1ecee2f246907bff44835f86e8f021de679a", + "https://deno.land/std@0.177.0/node/internal/normalize_encoding.mjs": "fd1d9df61c44d7196432f6e8244621468715131d18cc79cd299fc78ac549f707", + "https://deno.land/std@0.177.0/node/internal/primordials.mjs": "a72d86b5aa55d3d50b8e916b6a59b7cc0dc5a31da8937114b4a113ad5aa08c74", + "https://deno.land/std@0.177.0/node/internal/util.mjs": "f7fe2e1ca5e66f550ad0856b9f5ee4d666f0c071fe212ea7fc7f37cfa81f97a5", + "https://deno.land/std@0.177.0/node/internal/util/inspect.mjs": "11d7c9cab514b8e485acc3978c74b837263ff9c08ae4537fa18ad56bae633259", + "https://deno.land/std@0.177.0/node/internal/util/types.ts": "0e587b44ec5e017cf228589fc5ce9983b75beece6c39409c34170cfad49d6417", + "https://deno.land/std@0.177.0/node/internal/validators.mjs": "e02f2b02dd072a5d623970292588d541204dc82207b4c58985d933a5f4b382e6", + "https://deno.land/std@0.177.0/node/internal_binding/_libuv_winerror.ts": "30c9569603d4b97a1f1a034d88a3f74800d5ea1f12fcc3d225c9899d4e1a518b", + "https://deno.land/std@0.177.0/node/internal_binding/_node.ts": "cb2389b0eab121df99853eb6a5e3a684e4537e065fb8bf2cca0cbf219ce4e32e", + "https://deno.land/std@0.177.0/node/internal_binding/_utils.ts": "7c58a2fbb031a204dee9583ba211cf9c67922112fe77e7f0b3226112469e9fe1", + "https://deno.land/std@0.177.0/node/internal_binding/_winerror.ts": "3e8cfdfe22e89f13d2b28529bab35155e6b1730c0221ec5a6fc7077dc037be13", + "https://deno.land/std@0.177.0/node/internal_binding/buffer.ts": "31729e0537921d6c730ad0afea44a7e8a0a1044d070ade8368226cb6f7390c8b", + "https://deno.land/std@0.177.0/node/internal_binding/constants.ts": "21ff9d1ee71d0a2086541083a7711842fc6ae25e264dbf45c73815aadce06f4c", + "https://deno.land/std@0.177.0/node/internal_binding/string_decoder.ts": "54c3c1cbd5a9254881be58bf22637965dc69535483014dab60487e299cb95445", + "https://deno.land/std@0.177.0/node/internal_binding/types.ts": "2187595a58d2cf0134f4db6cc2a12bf777f452f52b15b6c3aed73fa072aa5fc3", + "https://deno.land/std@0.177.0/node/internal_binding/util.ts": "808ff3b92740284184ab824adfc420e75398c88c8bccf5111f0c24ac18c48f10", + "https://deno.land/std@0.177.0/node/internal_binding/uv.ts": "eb0048e30af4db407fb3f95563e30d70efd6187051c033713b0a5b768593a3a3", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", + "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", + "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", + "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", + "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", + "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.94.0/node/tty.ts": "9fa7f7b461759774b4eeab00334ac5d25b69bf0de003c02814be01e65150da79", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/ansi-styles/index.js": "7cc96ab93d1c9cfc0746e9dffb40be872e42ee242906f48e68df0d2c9669f737", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/has-flag/index.js": "aed21e4eba656057e7b8c6024305f5354d2ebee2adc857a1d8cd5207923de7e5", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/index.js": "6339123f32f7eb4b17c5c9c926ecdf3dbc353fd4fda7811ad2d3c1d4b98a7420", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/supports-color/index.js": "4d7f2d216b6ac9013d9ec7e004de21f5a7d00bf2be4075bab2d82638d0d41a86", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/templates.js": "f2e12be18cb84710e341e5499528280278052909fa74a12cefc9e2cc26a597ac", + "https://deno.land/x/chalk_deno@v4.1.1-deno/source/util.js": "cd08297ec411dcee91826ad01a00d3427235d4548ba605a59e64f0da83af8306", + "https://deno.land/x/hono@v3.0.0/adapter/deno/serve-static.ts": "d1c21498ced39849fa0bb23b372bf5d30677916fdbc875902735700ca1e789e3", + "https://deno.land/x/hono@v3.0.0/client/client.ts": "c720020a167139dfe8d6af7b91c0c5db38186722b380fb0bb251bae685a2103e", + "https://deno.land/x/hono@v3.0.0/client/index.ts": "7ad089b121f2613a0eaedd3d8aa8307a48bf3bf48f6c38281d6e73ee35f0d7ea", + "https://deno.land/x/hono@v3.0.0/client/types.ts": "47b83ab3dea4d6ef60a7c5ed11bfdb5113df7ded05403869e029f5317e44c827", + "https://deno.land/x/hono@v3.0.0/client/utils.ts": "781ec703f3e685cf02a201cfa717be111820ef89bebd7cb5968c6823310aebaa", + "https://deno.land/x/hono@v3.0.0/compose.ts": "9bc97f737857da061b2294eb9812b7e0ca773acc2e913c11bf93d5732c0469f0", + "https://deno.land/x/hono@v3.0.0/context.ts": "b053da7ec49c7a32f10572f951ae68347260ffab62fcce76cbc2ce544fe44d23", + "https://deno.land/x/hono@v3.0.0/hono.ts": "4929cefc738a4bd5efea2371f0c5338d27e9de91045b1c223ab98eda5a0364d5", + "https://deno.land/x/hono@v3.0.0/http-exception.ts": "e74a5e8504ecf795e9babdc2bab9d4052e1806a7396efc89f8e1ed83e813e3be", + "https://deno.land/x/hono@v3.0.0/middleware.ts": "d2c886e2f63b91b17f05a11f598ca101912c8fbfd66a25aef345b934ccfcd603", + "https://deno.land/x/hono@v3.0.0/middleware/basic-auth/index.ts": "0664ddf00c9f08a5109f92e93264678de55e90c341955c746bafa8f99ecab1eb", + "https://deno.land/x/hono@v3.0.0/middleware/bearer-auth/index.ts": "11d4ead9b57f5bcb2b6b4bf27076871f15da0e1e8828b2b79d90c15423357b47", + "https://deno.land/x/hono@v3.0.0/middleware/cache/index.ts": "2e86e089ebce01611ab5e231a6fbf7953ebabee3ab68362fcacea752ef48a0ab", + "https://deno.land/x/hono@v3.0.0/middleware/compress/index.ts": "0b8ddbd70688361d5ef4e9418afa28affe2500a7f41a231a87873dad26ef5548", + "https://deno.land/x/hono@v3.0.0/middleware/cors/index.ts": "10a743dcc793204835a5299070e2afaabb7e81cb742262faa9f1181f5e5d65ac", + "https://deno.land/x/hono@v3.0.0/middleware/etag/index.ts": "e679c30ddd2600087521a7c1dac3798c2319c09d203bb627b54357b984fc72e7", + "https://deno.land/x/hono@v3.0.0/middleware/html/index.ts": "a5028d8170dcc030d003749e743213e6532ff65798b741b81220207abc9af82d", + "https://deno.land/x/hono@v3.0.0/middleware/jsx/index.ts": "1925e4bf01ef1252b9bda6b0c4f79520b138ee319c41df3218cb3bf10c0ed248", + "https://deno.land/x/hono@v3.0.0/middleware/jwt/index.ts": "4af4649d9ae8ff2e767e53692d1974c956f523ab47a2560b869083cb493441ca", + "https://deno.land/x/hono@v3.0.0/middleware/logger/index.ts": "281b0fe431183a5d7b8d576645370efbd2737aeefaac7dc989d1c90dc03c52c0", + "https://deno.land/x/hono@v3.0.0/middleware/powered-by/index.ts": "7ec561885ac0410786f78aeb9789ed7869edb2d43c615cbc3d14f21baee87359", + "https://deno.land/x/hono@v3.0.0/middleware/pretty-json/index.ts": "f4a4b2fa2ecb73e23da6f0ef716fe7d6a7f05c69f64dc7f89fc68cbcb204a87f", + "https://deno.land/x/hono@v3.0.0/mod.ts": "e771d1c9f711b78f7540134e44a1ceda308fe0af58cb4c83ce7434c56c224670", + "https://deno.land/x/hono@v3.0.0/request.ts": "e3b38e76f7d13596266e644c8598d324c56ae73af263c3bf029f6ffeafdb221b", + "https://deno.land/x/hono@v3.0.0/router.ts": "21448bc2e6019574c10fae11237da4367037fa107e68bf3d049cd2fd0efd2adb", + "https://deno.land/x/hono@v3.0.0/router/reg-exp-router/index.ts": "52755829213941756159b7a963097bafde5cc4fc22b13b1c7c9184dc0512d1db", + "https://deno.land/x/hono@v3.0.0/router/reg-exp-router/node.ts": "8006b5bccb83d9fc98e0562a5545f6dd0be639ce445b089a6171c9c617aa8693", + "https://deno.land/x/hono@v3.0.0/router/reg-exp-router/router.ts": "35a405c855cf6d10c350a651169c2c728fd19aaf3d3a0c416e5a06752914e6bd", + "https://deno.land/x/hono@v3.0.0/router/reg-exp-router/trie.ts": "567493b301c44174f0895aedb8d055bbecf88f8a25626fa8ca61333bbd0c882c", + "https://deno.land/x/hono@v3.0.0/router/smart-router/index.ts": "74f9b4fe15ea535900f2b9b048581915f12fe94e531dd2b0032f5610e61c3bef", + "https://deno.land/x/hono@v3.0.0/router/smart-router/router.ts": "1d54f5c87875d856ed5fc2d22a100e1ff31debe3e9d8e9b1cc18d8e5706239f2", + "https://deno.land/x/hono@v3.0.0/router/trie-router/index.ts": "3eb75e7f71ba81801631b30de6b1f5cefb2c7239c03797e2b2cbab5085911b41", + "https://deno.land/x/hono@v3.0.0/router/trie-router/node.ts": "ca5b6a1ce6b6dc01003809bfa9cb93a323b37b27feee14c64655015deff7d2a9", + "https://deno.land/x/hono@v3.0.0/router/trie-router/router.ts": "0a969528a0c1680b552b20f0ca90e484e968ac279be9d5fd952b61a804d680e7", + "https://deno.land/x/hono@v3.0.0/types.ts": "a96ee9693b0de61b42016b80d3cbd958fabb795d8e4becb455845d70f66e7c87", + "https://deno.land/x/hono@v3.0.0/utils/body.ts": "b6b5ed679122968a74845df4c5454c677f09adc4f3466d822f3b1397884e540e", + "https://deno.land/x/hono@v3.0.0/utils/buffer.ts": "d28ab08d2571e890ec2ad7ce4c0318a503094f8403eac3d5eb18a8e5c23b29b2", + "https://deno.land/x/hono@v3.0.0/utils/cookie.ts": "545872bd7af3b455c24fd386ecbccfd161e7d4a0038d6b09b1bb22723602f90a", + "https://deno.land/x/hono@v3.0.0/utils/crypto.ts": "bda0e141bbe46d3a4a20f8fbcb6380d473b617123d9fdfa93e4499410b537acc", + "https://deno.land/x/hono@v3.0.0/utils/encode.ts": "b628be2de7ab48cc806b1e5b93b3baf6886b7656d6d65dfa80f4fcd9c0f63a5f", + "https://deno.land/x/hono@v3.0.0/utils/filepath.ts": "5f708bb6a2f0b8e83a3333868ac86abdfba15e52b46acb5e13018fa2138f0826", + "https://deno.land/x/hono@v3.0.0/utils/html.ts": "636c4a04eaea1c52c14a37cd28d83cba66b293d1e31420a4475e951268901ae4", + "https://deno.land/x/hono@v3.0.0/utils/http-status.ts": "2d6003e352c1fe918db663fa4bd2b20bf0b9d4e1699ba5e163f317f00b29d938", + "https://deno.land/x/hono@v3.0.0/utils/jwt/index.ts": "5e4b82a42eb3603351dfce726cd781ca41cb57437395409d227131aec348d2d5", + "https://deno.land/x/hono@v3.0.0/utils/jwt/jwt.ts": "4fefadfb914ad88282949d0cce2be16bdbd0b0614d15ecbb340c4fc47a9929b9", + "https://deno.land/x/hono@v3.0.0/utils/jwt/types.ts": "715514fe59f0c37048b2940528bd4f4ec5795e04f0bc1d6d7e33f8d8bb1fe9de", + "https://deno.land/x/hono@v3.0.0/utils/mime.ts": "e17bdbac85b97c3d223c48874c2abe867e0720461e9f7e0c340141c080b9c6d6", + "https://deno.land/x/hono@v3.0.0/utils/types.ts": "173dedfe018b447cc6b067d2b6968c1f1dccba67ad50526d356b79e0465a5753", + "https://deno.land/x/hono@v3.0.0/utils/url.ts": "2cf0f38d976761296ccaf2622b6999b7c0c1d3a1f63f066a6f832288ea7c28d9", + "https://deno.land/x/hono@v3.0.0/validator/index.ts": "3dc2c9418dee74333ea8b98642ce112b6d449042bbcefe1e835047fcf2458170", + "https://deno.land/x/hono@v3.0.0/validator/validator.ts": "9b2c9983b6b8a31cbc1f687096e232fb159004c15421e39cb050da2ac0f78c95", + "https://deno.land/x/hono@v4.3.11/adapter/deno/serve-static.ts": "db226d30f08f1a8bb77653ead42a911357b2f8710d653e43c01eccebb424b295", + "https://deno.land/x/hono@v4.3.11/compose.ts": "37d6e33b7db80e4c43a0629b12fd3a1e3406e7d9e62a4bfad4b30426ea7ae4f1", + "https://deno.land/x/hono@v4.3.11/context.ts": "facfd749d823a645039571d66d9d228f5ae6836818b65d3b6c4c6891adfe071e", + "https://deno.land/x/hono@v4.3.11/hono-base.ts": "fd7e9c1bba1e13119e95158270011784da3a7c3014c149ba0700e700f840ae0d", + "https://deno.land/x/hono@v4.3.11/hono.ts": "23edd0140bf0bd5a68c14ae96e5856a5cec6b844277e853b91025e91ea74f416", + "https://deno.land/x/hono@v4.3.11/http-exception.ts": "f5dd375e61aa4b764eb9b99dd45a7160f8317fd36d3f79ae22585b9a5e8ad7c5", + "https://deno.land/x/hono@v4.3.11/middleware/serve-static/index.ts": "14b760bbbc4478cc3a7fb9728730bc6300581c890365b7101b80c16e70e4b21e", + "https://deno.land/x/hono@v4.3.11/request.ts": "7b08602858e642d1626c3106c0bedc2aa8d97e30691a079351d9acef7c5955e6", + "https://deno.land/x/hono@v4.3.11/router.ts": "880316f561918fc197481755aac2165fdbe2f530b925c5357a9f98d6e2cc85c7", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/index.ts": "52755829213941756159b7a963097bafde5cc4fc22b13b1c7c9184dc0512d1db", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/node.ts": "7efaa6f4301efc2aad0519c84973061be8555da02e5868409293a1fd98536aaf", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/router.ts": "632f2fa426b3e45a66aeed03f7205dad6d13e8081bed6f8d1d987b6cad8fb455", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/trie.ts": "852ce7207e6701e47fa30889a0d2b8bfcd56d8862c97e7bc9831e0a64bd8835f", + "https://deno.land/x/hono@v4.3.11/router/smart-router/index.ts": "74f9b4fe15ea535900f2b9b048581915f12fe94e531dd2b0032f5610e61c3bef", + "https://deno.land/x/hono@v4.3.11/router/smart-router/router.ts": "dc22a8505a0f345476f07dca3054c0c50a64d7b81c9af5a904476490dfd5cbb4", + "https://deno.land/x/hono@v4.3.11/router/trie-router/index.ts": "3eb75e7f71ba81801631b30de6b1f5cefb2c7239c03797e2b2cbab5085911b41", + "https://deno.land/x/hono@v4.3.11/router/trie-router/node.ts": "d3e00e8f1ba7fb26896459d5bba882356891a07793387c4655d1864c519a91de", + "https://deno.land/x/hono@v4.3.11/router/trie-router/router.ts": "54ced78d35676302c8fcdda4204f7bdf5a7cc907fbf9967c75674b1e394f830d", + "https://deno.land/x/hono@v4.3.11/types.ts": "b561c3ee846121b33c2d81331246cdedf7781636ed72dad7406677105b4275de", + "https://deno.land/x/hono@v4.3.11/utils/body.ts": "774cb319dfbe886a9d39f12c43dea15a39f9d01e45de0323167cdd5d0aad14d4", + "https://deno.land/x/hono@v4.3.11/utils/filepath.ts": "a83e5fe87396bb291a6c5c28e13356fcbea0b5547bad2c3ba9660100ff964000", + "https://deno.land/x/hono@v4.3.11/utils/html.ts": "6ea4f6bf41587a51607dff7a6d2865ef4d5001e4203b07e5c8a45b63a098e871", + "https://deno.land/x/hono@v4.3.11/utils/http-status.ts": "f5b820f2793e45209f34deddf147b23e3133a89eb4c57dc643759a504706636b", + "https://deno.land/x/hono@v4.3.11/utils/mime.ts": "d1fc2c047191ccb01d736c6acf90df731324536298181dba0ecc2259e5f7d661", + "https://deno.land/x/hono@v4.3.11/utils/types.ts": "050bfa9dc6d0cc4b7c5069944a8bd60066c2f9f95ee69833623ad104f11f92bf", + "https://deno.land/x/hono@v4.3.11/utils/url.ts": "855169632c61d03703bd08cafb27664ba3fdb352892f01687d5cce8fd49e3cb1", + "https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0", + "https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995", + "https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84", + "https://deno.land/x/imagescript@1.3.0/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d", + "https://deno.land/x/imagescript@1.3.0/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d", + "https://deno.land/x/imagescript@1.3.0/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c", + "https://deno.land/x/imagescript@1.3.0/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8", + "https://deno.land/x/imagescript@1.3.0/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d", + "https://deno.land/x/imagescript@1.3.0/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f", + "https://deno.land/x/imagescript@1.3.0/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6", + "https://deno.land/x/imagescript@1.3.0/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012", + "https://deno.land/x/imagescript@1.3.0/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f", + "https://deno.land/x/imagescript@1.3.0/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814", + "https://deno.land/x/imagescript@1.3.0/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea", + "https://deno.land/x/imagescript@1.3.0/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93", + "https://deno.land/x/imagescript@1.3.0/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e", + "https://deno.land/x/imagescript@1.3.0/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa", + "https://deno.land/x/imagescript@1.3.0/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9", + "https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9", + "https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b", + "https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d", + "https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d", + "https://deno.land/x/socket_io@0.2.0/deps.ts": "136b3fc7c55f2a06a367965da3395f1bf7de66d36a0d91c5f795084fa8c67ab3", + "https://deno.land/x/socket_io@0.2.0/mod.ts": "61277a4145c378b602e8146ed4302117f130cf3b018c0a07bcb917e1b8c9fcf4", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io-parser/base64-arraybuffer.ts": "57ccea6679609df5416159fcc8a47936ad28ad6fe32235ef78d9223a3a823407", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io-parser/mod.ts": "27d35094e2159ba49f6e74f11ed83b6208a6adb5a2d5ab3cbbdcdc9dc0e36ae7", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/cors.ts": "e39b530dc3526ef85f288766ce592fa5cce2ec38b3fa19922041a7885b79b67c", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/server.ts": "1321852222ccf6b656787881fe0112c2a62930beaf1a56b6f5b327511323176f", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/socket.ts": "b4ae4e2fad305c6178785a1a2ae220e38dfb31dc0ae43759c3d3a4f96ca48c9b", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/transport.ts": "8d09ae6bde2f71942cfbae96265aa693387e054776cf2ef5a3b4f8aafa9a427f", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/transports/polling.ts": "3d3cf369eb430360b57eaf18c74fb7783a1641ed8613c460cdfa8f663ca66be4", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/transports/websocket.ts": "fd818e91e10c55b587a221669f90cc79df42574f781e50ef73bf3539fd9bcfee", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/lib/util.ts": "9f396a141422c8a2e2ef4cbb31c8b7ec96665d8f1ca397888eaaa9ad28ca8c65", + "https://deno.land/x/socket_io@0.2.0/packages/engine.io/mod.ts": "3f7d85ebd3bee6e17838f4867927d808f35090a71e088fd4dd802e3255d44c4a", + "https://deno.land/x/socket_io@0.2.0/packages/event-emitter/mod.ts": "dcb2cb9c0b409060cf15a6306a8dbebea844aa3c58f782ed1d4bc3ccef7c2835", + "https://deno.land/x/socket_io@0.2.0/packages/msgpack/lib/decode.ts": "5906fa37474130b09fe308adb53c95e40d2484a015891be3249fb2f626c462bb", + "https://deno.land/x/socket_io@0.2.0/packages/msgpack/lib/encode.ts": "15dab78be56d539c03748c9d57086f7fd580eb8fbe2f8209c28750948c7d962e", + "https://deno.land/x/socket_io@0.2.0/packages/msgpack/mod.ts": "c7f4a9859af3e0b23794b400546d93475b19ba5110a02245112a0a994a31d309", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io-parser/mod.ts": "44479cf563b0ad80efedd1059cd40114bc5db199b45e623c2764e80f4a264f8c", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io-redis-adapter/mod.ts": "45d6b7f077f94fec385152bda7fda5ac3153c2ca3548cf4859891af673fa97cc", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/adapter.ts": "8f606f3fe57712221a73a6b01aa8bf1be39c4530ec8ebb8d2905d5313d4da9c4", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/broadcast-operator.ts": "d842eb933acc996a05ac701f6d83ffee49ee9c905c9adbdee70832776045bf63", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/client.ts": "b78e965dc3ab35d2fb9ccb859f4e1ce43d7c830aae9448d4958fa8ef9627eb4d", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/namespace.ts": "920f16545ec4220831b0aa2164e256915c7f4dec318d1527efeae1596b831fe3", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/parent-namespace.ts": "2d7f8a70498d161856aec522ae2f98727d58c5a9c252ad51a6ab5830b4fa9e2e", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/server.ts": "bd450bea5573bb6144a5eded1c03dda93cb3ed8c8c671a6de0261f97307d6c71", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/lib/socket.ts": "7b88e37eabd31ce21039321325f51c955d9123133506acf3af692bf9337f081b", + "https://deno.land/x/socket_io@0.2.0/packages/socket.io/mod.ts": "dfd465bdcf23161af0c4d79fb8fc8912418c46a20d15e8b314cec6d9fb508196", + "https://deno.land/x/socket_io@0.2.0/test_deps.ts": "1f9dfa07a1e806ccddc9fa5f7255338d9dff67c40d7e83795f4f0f7bd710bde9", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/backoff.ts": "33e4a6e245f8743fbae0ce583993a671a3ac2ecee433a3e7f0bd77b5dd541d84", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/command.ts": "802df3a1f49f6c49fe3e8fcf13fd0cc360b8a02369de0310a72d7f0c8e4ceaab", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/connection.ts": "c31d2e0cb360bc641e7286f1d53cf58790fbcda025c06887f84a821f39d0fdff", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/errors.ts": "bc8f7091cb9f36cdd31229660e0139350b02c26851e3ac69d592c066745feb27", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/executor.ts": "03e5f43df4e0c9c62b0e1be778811d45b6a1966ddf406e21ed5a227af70b7183", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/mod.ts": "20908f005f5c102525ce6aa9261648c95c5f61c6cf782b2cbb2fce88b1220f69", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/pipeline.ts": "80cc26a881149264d51dd019f1044c4ec9012399eca9f516057dc81c9b439370", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/protocol/_util.ts": "0525f7f444a96b92cd36423abdfe221f8d8de4a018dc5cb6750a428a5fc897c2", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/protocol/command.ts": "b1efd3b62fe5d1230e6d96b5c65ba7de1592a1eda2cc927161e5997a15f404ac", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/protocol/mod.ts": "f2601df31d8adc71785b5d19f6a7e43dfce94adbb6735c4dafc1fb129169d11a", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/protocol/reply.ts": "beac2061b03190bada179aef1a5d92b47a5104d9835e8c7468a55c24812ae9e4", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/protocol/types.ts": "40b0a568cb7fd4dc9107997062584d24e5c6ffa1f21acb6410aa19c92f89e9e1", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/pubsub.ts": "324b87dae0700e4cb350780ce3ae5bc02780f79f3de35e01366b894668b016c6", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/redis.ts": "a5c2cf8c72e7c92c9c8c6911f98227062649f6cba966938428c5414200f3aa54", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/stream.ts": "f116d73cfe04590ff9fa8a3c08be8ff85219d902ef2f6929b8c1e88d92a07810", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/vendor/https/deno.land/std/async/deferred.ts": "7391210927917113e04247ef013d800d54831f550e9a0b439244675c56058c55", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/vendor/https/deno.land/std/async/delay.ts": "c7e2604f7cb5ef00d595de8dd600604902d5be03a183b515b3d6d4bbb48e1700", + "https://deno.land/x/socket_io@0.2.0/vendor/deno.land/x/redis@v0.27.1/vendor/https/deno.land/std/io/buffer.ts": "8c5f84b7ecf71bc3e12aa299a9fae9e72e495db05281fcdd62006ecd3c5ed3f3" + }, + "workspace": { + "dependencies": [ + "jsr:@hono/hono@^4.6.11" + ], + "packageJson": { + "dependencies": [ + "npm:fs@^0.0.1-security", + "npm:http@^0.0.1-security", + "npm:nodemon@^3.1.7", + "npm:path@~0.12.7", + "npm:socket.io@^4.8.1", + "npm:zod@^3.24.1" + ] + } + } +} diff --git a/io.ts b/io.ts new file mode 100644 index 0000000..42b0c0c --- /dev/null +++ b/io.ts @@ -0,0 +1,475 @@ +// deno-lint-ignore-file no-explicit-any +import { Server } from "https://deno.land/x/socket_io@0.2.0/mod.ts"; +import { decode } from 'hono/jwt'; +import chalk from "https://deno.land/x/chalk_deno@v4.1.1-deno/source/index.js"; +import { z } from "zod"; + +import * as world from "./constants/world.ts"; +import * as items from "./constants/items.ts"; +import * as utils from "./utils.ts"; +import { LocalPlayer, PlayerCrumb, ShopData, CritterId } from "./types.ts"; + +import parties from "./constants/parties.json" with { type: "json" }; +import itemsJSON from "./public/base/items.json" with { type: "json" }; + +export const io = new Server(); +io.on("connection", (socket) => { + let localPlayer: LocalPlayer; + + /** Condensed player data that is sufficient enough for other clients */ + let localCrumb: PlayerCrumb; + + // TODO: implement checking PlayFab API with ticket + socket.once("login", async (ticket: string) => { + if (z.object({ + ticket: z.string() + }).safeParse({ ticket: ticket }).success == false) return; + + let playerData; + try { + playerData = decode(ticket); + } catch(_e) { + socket.disconnect(true); + return + } + + // TODO: make this just an inline function instead of having an onPropertyChange function, I'm just really lazy right now lol -index + function onPropertyChange(property: string, value: any) { + utils.updateAccount(localPlayer.nickname, property, value); + } + + const createArrayHandler = (propertyName: string) => ({ + get(target: any, property: string) { + if (typeof target[property] === 'function') { + return function (...args: any[]) { + const result = target[property].apply(target, args); + onPropertyChange(propertyName, target); + return result; + }; + } + return target[property]; + } + }); + + const handler = { + set(target: any, property: string, value: any) { + if (Array.isArray(value)) { + target[property] = new Proxy(value, createArrayHandler(property)); + onPropertyChange(property, target[property]); + } else { + target[property] = value; + onPropertyChange(property, value); + } + return true; + }, + get(target: any, property: string) { + if (Array.isArray(target[property])) { + return new Proxy(target[property], createArrayHandler(property)); + } + return target[property]; + } + }; + + const payload = playerData.payload; + const sub = payload.sub as { + playerId: string, + nickname: string, + critterId: CritterId, + partyId: string, + persistent: boolean, + mods: Array + }; + const persistentAccount = await utils.getAccount(sub.nickname); + if (!sub.persistent || persistentAccount.individual == null) { + localPlayer = { + playerId: sub.playerId, + nickname: sub.nickname, + critterId: sub.critterId, + ignore: [], + friends: [], + inventory: [], + gear: [], + eggs: [], + coins: 150, + isMember: false, + isGuest: false, + isTeam: false, + x: 0, + y: 0, + rotation: 0, + mutes: [], + + _partyId: sub.partyId, // This key is replaced down the line anyway + _mods: [] + }; + + if (sub.persistent) { + utils.createAccount(localPlayer); + localPlayer = new Proxy(utils.expandAccount(localPlayer), handler); + }; + } else { + persistentAccount.individual.critterId = sub.critterId || "hamster"; + persistentAccount.individual._partyId = sub.partyId || "default"; + persistentAccount.individual._mods = sub.mods || []; + + localPlayer = new Proxy(utils.expandAccount(persistentAccount.individual), handler); + } + + localPlayer._partyId = socket.handshake.query.get('partyId') || 'default'; + world.queue.splice(world.queue.indexOf(localPlayer.nickname), 1); + + localCrumb = utils.makeCrumb(localPlayer, world.spawnRoom); + socket.join(world.spawnRoom); + + world.players[localPlayer.playerId] = localCrumb; + socket.emit("login", { + player: localPlayer, + spawnRoom: world.spawnRoom, + }); + }); + + socket.on("joinRoom", (roomId: string) => { + if (z.object({ + roomId: z.enum(Object.keys(world.rooms) as any) + }).safeParse({ roomId: roomId }).success == false) return; + + const _room = (world.rooms[roomId] || { default: null }).default; + if (!_room) return; + + socket.leave(localCrumb._roomId); + socket.broadcast.in(localCrumb._roomId).emit("R", localCrumb); + + const modEnabled = (localPlayer._mods || []).includes('roomExits'); + //@ts-ignore: Index type is correct + const correctExit = world.roomExits[localCrumb._roomId + "->" + roomId] + if (modEnabled && correctExit) { + localPlayer.x = correctExit.x; + localPlayer.y = correctExit.y; + localPlayer.rotation = correctExit.r; + }; + + if (!modEnabled || !correctExit) { + localPlayer.x = _room.startX; + localPlayer.y = _room.startY; + localPlayer.rotation = _room.startR | 180; + }; + + localCrumb = utils.makeCrumb(localPlayer, roomId); + world.players[localPlayer.playerId] = localCrumb; + + console.log(chalk.green('> ' + localPlayer.nickname + ' joined "' + roomId + '"!')); + socket.join(roomId); + + let playerCrumbs = Object.values(world.players).filter((crumb) => crumb._roomId == roomId); + if (world.npcs[roomId]) { + playerCrumbs = [ + ...playerCrumbs, + ...world.npcs[roomId] + ]; + }; + socket.emit("joinRoom", { + name: _room.name, + roomId: roomId, + playerCrumbs: playerCrumbs + }); + + socket.broadcast.in(localCrumb._roomId).emit("A", localCrumb); + }); + + socket.on("moveTo", (x: number, y: number) => { + const roomData = world.rooms[localCrumb._roomId][localPlayer._partyId]; + if (z.object({ + x: z.number().min(0).max(roomData.width), + y: z.number().min(0).max(roomData.height) + }).safeParse({ x: x, y: y }).success == false) return; + + const newDirection = utils.getDirection(localPlayer.x, localPlayer.y, x, y); + + localPlayer.x = x; + localPlayer.y = y; + localPlayer.rotation = newDirection; + + localCrumb.x = x; + localCrumb.y = y; + localCrumb.r = newDirection; + + io.in(localCrumb._roomId).volatile.emit("X", { + i: localPlayer.playerId, + x: x, + y: y, + r: newDirection + }); + }); + + socket.on("message", (text: string) => { + if (z.object({ + text: z.string().nonempty() + }).safeParse({ text: text }).success == false) return; + + console.log(chalk.gray(`> ${localPlayer.nickname} sent message: "%s"`), text); + localCrumb.m = text; + + socket.broadcast.in(localCrumb._roomId).emit("M", { + i: localPlayer.playerId, + m: text + }); + + setTimeout(() => { + if (localCrumb.m != text) return; + localCrumb.m = ""; + }, 5e3); + }); + + socket.on("emote", (emote: string) => { + if (z.object({ + emote: z.string().nonempty() // TODO: make this an enum + }).safeParse({ emote: emote }).success == false) return; + + console.log(chalk.gray(`> ${localPlayer.nickname} sent emote: %s`), emote); + localCrumb.e = emote; + + socket.broadcast.in(localCrumb._roomId).emit("E", { + i: localPlayer.playerId, + e: emote + }); + + setTimeout(() => { + if (localCrumb.e != emote) return; + localCrumb.e = ""; + }, 5e3); + }); + + // ? Options is specified just because sometimes it is sent, but its always an empty string + socket.on("code", (code: string, _options?: string) => { + if (z.object({ + command: z.enum([ + "pop", + "freeitem", + "tbt", + "darkmode", + "spydar", + "allitems" + ]) + }).safeParse({ + command: code + }).success == false) return; + + console.log(chalk.gray(`> ${localPlayer.nickname} sent code: %s`), code); + + const addItem = function(id: string, showGUI: boolean = false) { + if (!localPlayer.inventory.includes(id)) { + socket.emit("addItem", { itemId: id, showGUI: showGUI }); + localPlayer.inventory.push(id); + } + } + + // Misc. Codes + switch(code) { + case 'pop': { + socket.emit("pop", Object.values(world.players).filter((critter) => critter.c != "huggable").length); + break; + } + case 'freeitem': { + addItem(items.shop.freeItem.itemId, true); + break; + } + case 'tbt': { + const _throwbackItem = utils.getNewCodeItem(localPlayer, items.throwback); + if (_throwbackItem) addItem(_throwbackItem, true); + break; + } + case 'darkmode': { + addItem("3d_black", true); + break; + } + case 'spydar': { + localPlayer.gear = [ + "sun_orange", + "super_mask_black", + "toque_blue", + "dracula_cloak", + "headphones_black", + "hoodie_black" + ]; + + if (localCrumb._roomId == "tavern") { + localPlayer.x = 216; + localPlayer.y = 118; + + localCrumb.x = 216; + localCrumb.y = 118; + + io.in(localCrumb._roomId).volatile.emit("X", { + i: localPlayer.playerId, + x: 216, + y: 118 + }); + } + + io.in(localCrumb._roomId).emit("G", { + i: localPlayer.playerId, + g: localPlayer.gear + }); + + socket.emit("updateGear", localPlayer.gear); + break; + } + case 'allitems': { + for (const item of itemsJSON) { + addItem(item.itemId, false); + } + break; + } + }; + + // Item Codes + const _itemCodes = items.codes as Record> + const item = _itemCodes[code]; + + if (typeof(item) == "string") { + addItem(item, true); + } else if (typeof(item) == "object") { + for (const _ of item) { + addItem(_, true); + } + } + + // Event Codes (eg. Christmas 2019) + const _eventItemCodes = items.eventCodes as Record>; + const eventItem = (_eventItemCodes[localPlayer._partyId] || {})[code]; + if (eventItem) addItem(eventItem); + }); + + socket.on("updateGear", (gear: Array) => { + if (z.object({ + gear: z.array(z.string().nonempty()).default([]) + }).strict().safeParse({ gear: gear }).success == false) return; + + const _gear = []; + for (const itemId of gear) { + if (localPlayer.inventory.includes(itemId)) { + _gear.push(itemId) + } + } + localPlayer.gear = _gear; + + io.in(localCrumb._roomId).emit("G", { + i: localPlayer.playerId, + g: localPlayer.gear + }); + + socket.emit("updateGear", localPlayer.gear); + }); + + socket.on("getShop", () => { + const _shopItems = items.shop as unknown as ShopData; + socket.emit("getShop", { + lastItem: _shopItems.lastItem.itemId, + freeItem: _shopItems.freeItem.itemId, + nextItem: _shopItems.nextItem.itemId, + collection: _shopItems.collection.map((item) => item.itemId) + }) + }); + + socket.on("buyItem", (itemId: string) => { + if (z.object({ + itemId: z.string().nonempty() + }).strict().safeParse({ itemId: itemId }).success == false) return; + + // ? Free item is excluded from this list because the game just sends the "/freeitem" code + const currentShop = items.shop; + const _shopItems = [currentShop.lastItem, currentShop.nextItem, ...currentShop.collection] + + const target = _shopItems.find((item) => item.itemId == itemId)!; + if (!target) { + console.log(chalk.red("> There is no item in this week's shop with itemId: %s"), itemId); + return; + }; + + if (localPlayer.coins >= target.cost && !localPlayer.inventory.includes(itemId)) { + console.log(chalk.green("[+] Bought item: %s for %d coins"), itemId, target.cost); + localPlayer.coins -= target.cost; + localPlayer.inventory.push(itemId); + + socket.emit("buyItem", { itemId: itemId }); + socket.emit("updateCoins", { balance: localPlayer.coins }); + } + }); + + socket.on("trigger", async () => { + const activatedTrigger = await utils.getTrigger(localPlayer, localCrumb._roomId, localPlayer._partyId); + if (!activatedTrigger) return; + + if (activatedTrigger.hasItems) { + for (const item of activatedTrigger.hasItems) { + if (!localPlayer.inventory.includes(item)) return; + } + } + + if (activatedTrigger.grantItem) { + let items = activatedTrigger.grantItem; + if (typeof(items) == 'string') items = [items]; + + for (const item of items) { + if (!localPlayer.inventory.includes(item)) { + socket.emit("addItem", { itemId: item, showGUI: true }); + localPlayer.inventory.push(item); + } + } + } + + if (activatedTrigger.addEgg) { + const egg = activatedTrigger.addEgg; + socket.emit("addEgg", egg); + localPlayer.eggs.push(egg); + } + }); + + socket.on("addIgnore", (playerId: string) => { + if (z.object({ + playerId: z.enum(Object.keys(world.players) as any) + }).strict().safeParse({ playerId: playerId }).success == false) return; + + if (Object.keys(world.players).includes(playerId) && !localPlayer.ignore.includes(playerId)) { + localPlayer.ignore.push(playerId); + }; + }); + + socket.on("attack", (playerId: string) => { + if (z.object({ + playerId: z.enum(Object.keys(world.players) as any) + }).strict().safeParse({ playerId: playerId }).success == false) return; + + if (!localPlayer.gear.includes('bb_beebee')) return; + const monster = Object.values(world.players).find((player) => player.i == playerId && player.c == "huggable"); + + if (monster) { + io.in(localCrumb._roomId).emit("R", monster); + + localPlayer.coins += 10; + socket.emit("updateCoins", { balance: localPlayer.coins }); + + delete world.players[playerId]; + }; + }); + + socket.on("switchParty", (partyId: string) => { + if (z.object({ + partyId: z.enum(parties as any) + }).strict().safeParse({ partyId: partyId }).success == false) return; + + localPlayer._partyId = partyId; + socket.emit("switchParty"); + }); + + socket.on("beep", () => socket.emit("beep")); + + socket.on("disconnect", (reason) => { + if (reason == "server namespace disconnect") return; + + if (localPlayer && localCrumb) { + io.in(localCrumb._roomId).emit("R", localCrumb); + delete world.players[localPlayer.playerId]; + }; + }); +}); \ No newline at end of file diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..2a9d316 --- /dev/null +++ b/main.ts @@ -0,0 +1,144 @@ +import { serve } from "https://deno.land/std@0.162.0/http/server.ts"; + +import { sign } from 'hono/jwt'; +import { Hono } from "https://deno.land/x/hono@v3.0.0/mod.ts"; +import { serveStatic } from "https://deno.land/x/hono@v3.0.0/middleware.ts"; +import { env } from 'hono/adapter'; +import { validator } from 'hono/validator'; + +import { io } from "./io.ts"; +import * as world from "./constants/world.ts"; +import { Room } from "./types.ts"; +import * as schemas from "./schema.ts"; +import { getAccount } from "./utils.ts"; +import parties from "./constants/parties.json" with { type: 'json' }; + +const app = new Hono(); +app.get('/*', serveStatic({ root: './public' })); + +// APIs for debugging and other purposes +app.get('/api/server/players', (c) => c.json({ players: world.players })); + +app.get('/api/server/rooms', (c) => c.json(world.rooms)); + +app.get('/api/server/persistence', async (c) => { + const account = await getAccount(); + return c.json({ + success: true, + data: account + }); +}) + +// APIs for use by the client +app.post('/api/client/login', validator('json', async (_value, c) => { + try { + const body = await c.req.json(); + const parsed = schemas.login.safeParse(body); + if (!parsed.success) { + return c.json({ + success: false, + message: "Validation failure", + error: parsed.error + }, 400); + }; + return parsed.data; + } catch(_e) { + return c.json({ + success: false, + message: "Bad request" + }, 400); + } +// deno-lint-ignore no-explicit-any +}) as any, async (c) => { + const body = c.req.valid('json') as { + nickname: string, + critterId: string, + partyId: string, + persistent: boolean, + mods: Array + }; + + const _players = Object.values(world.players); + if (_players.find((player) => player.n == body.nickname) || world.queue.includes(body.nickname)) { + return c.json({ + success: false, + message: "There is already a player with this nickname online." + }); + } + + const JWT_CONTENT = { + sub: { + playerId: crypto.randomUUID(), + ...body // ZOD validator is set to make the body strict, so this expansion should be fine + }, + exp: Math.floor(Date.now() / 1000) + 60 * 5 // 5 mins expiry + }; + + //@ts-ignore: Deno lint + const { JWT_TOKEN } = env<{ JWT_TOKEN: string }>(c); + const token = await sign(JWT_CONTENT, JWT_TOKEN); + + world.queue.push(body.nickname); + return c.json({ + success: true, + playerId: JWT_CONTENT.sub.playerId, + token: token + }); +}); + +app.get('/api/client/rooms', (c) => { + const partyId = c.req.query('partyId') || 'default'; + if (!parties.includes(partyId)) { + return c.json({ + success: false, + message: "Invalid partyId hash provided." + }); + } + + let missing = 0; + const roomResponse = Object.keys(world.rooms).reduce((res: Array, roomId) => { + const room = world.rooms[roomId]; + + if (room[partyId]) { + if (!room[partyId].partyExclusive || room[partyId].partyExclusive.includes(partyId)) { + res.push(room[partyId]); + } else { + missing++; + } + } else { + if (!room.default.partyExclusive || room.default.partyExclusive.includes(partyId)) { + res.push(room.default); + } else { + missing++; + } + } + return res; + }, []); + + if (missing == Object.keys(world.rooms).length) { + return c.json({ + success: false, + message: "No rooms were fetched while indexxing using the specified partyId hash." + }); + } + + const res = roomResponse.filter((room) => room != null); + if (c.req.query('debug')) { + const roomNames = res.map((room) => room.name); + return c.json({ + parties: parties, + data: roomNames + }); + } + + return c.json({ + parties: parties, + data: res + }); +}); + +const handler = io.handler(async (req) => { + return await app.fetch(req); +}); + +await serve(handler, { port: 3257 }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e01acbf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,573 @@ +{ + "name": "box-critters-revival", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "box-critters-revival", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "fs": "^0.0.1-security", + "http": "^0.0.1-security", + "nodemon": "^3.1.7", + "path": "^0.12.7", + "socket.io": "^4.8.1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/http": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", + "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..04807ee --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "box-critters-revival", + "version": "1.0.0", + "description": "", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon main.js", + "dev": "nodemon main.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "fs": "^0.0.1-security", + "http": "^0.0.1-security", + "nodemon": "^3.1.7", + "path": "^0.12.7", + "socket.io": "^4.8.1", + "zod": "^3.24.1" + } +} diff --git a/schema.ts b/schema.ts new file mode 100644 index 0000000..968740d --- /dev/null +++ b/schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; +import parties from './constants/parties.json' with { type: 'json' }; + +/* + LOGIN API +*/ +export const login = z.object({ + nickname: z.string().nonempty().max(25), + critterId: z.enum([ + "hamster", + "beaver", + "lizard", + "raccoon", + "penguin", + "snail", + "snow_greeter", + "snowkeeper", + "snowgirl", + "snow_patrol", + "snowgrandma" + ]).default("hamster"), + partyId: z.enum(parties as [string, ...string[]]).default("default"), + persistent: z.boolean().default(false), + mods: z.array(z.enum(["roomExits"])).default([]) +}).strict(); // Strict to disallow extra keys \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..413f1b9 --- /dev/null +++ b/types.ts @@ -0,0 +1,124 @@ +export type CritterId = "hamster" | "snail" | "lizard" | "beaver" | "raccoon" | "penguin" | "huggable"; + +export type Trigger = { + hex: string, + world?: { joinRoom: string }, + room?: { hide: Array }, + server?: { + grantItem?: string | Array, + hasItems?: Array, + joinGame?: string, + addEgg?: string + } +} + +export type Room = { + roomId: string, + name: string, + width: number, + height: number, + startX: number, + startY: number, + startR: number, + media: { + background: string, + foreground?: string, + treasure?: string, + navMesh: string, + music?: string, + video?: string, + }, + layout: string, + triggers: Array, + spriteSheet: string, + extra: null, + partyExclusive?: Array +} + +export type LocalPlayer = { + playerId: string, + nickname: string, + critterId: CritterId, + ignore: Array, + friends: Array, + inventory: Array, + gear: Array, + /** Eggs is the term used to describe any object used in a scavenger hunt. Any prop name found in that list will be hidden and replaced with it's "_found" suffix prop counterpart */ + eggs: Array, + coins: number, + isMember: boolean | false, + isGuest: boolean | false, + isTeam: boolean | false, + x: number | 440, + y: number | 210, + rotation: number | 180, + mutes: Array, + + _partyId: string, + _mods: Array, + + // deno-lint-ignore no-explicit-any + [key: string]: any +} + +export type PlayerCrumb = { + /** Player ID */ + i: string, + /** Player Nickname */ + n: string, + /** Critter (Hamster, Beaver, Lizard, Snail, etc) */ + c: CritterId, + x: number, + y: number, + r: number, + /** Gear (equipped items) */ + g: Array, + + /** Message */ + m: string, + /** Emote */ + e: string, + + _roomId: string +} + +export type ShopData = { + lastItem: { itemId: string, cost: number }, + freeItem: { itemId: string, cost: number }, + nextItem: { itemId: string, cost: number }, + collection: Array<{ itemId: string, cost: number }> +} + +/* + Socket.io +*/ +export interface ServerToClientEvents { + login: () => {player: LocalPlayer}; + updateGear: () => { i: number, g: Array }; + updateCoins: () => { balance: number }; + addItem: () => { itemId: string }; + addEgg: () => string; + A: () => PlayerCrumb; + R: () => PlayerCrumb; + X: () => { i: number, x: number, y: number }; + M: () => { i: number, m: string }; + E: () => { i: number, e: string }; + G: () => { i: number, g: Array }; +} + +export interface ClientToServerEvents { + login: (ticket: string) => void; + joinLobby: () => unknown; // Unsure what this is for, I don't think the game had several servers + joinRoom: (roomId: string) => { name: string, roomId: string, playerCrumbs: Array }; + message: (text: string) => void; + emote: (emote: string) => void; + code: (code: string, options?: string) => void; + addIgnore: (id: number) => void; + addFriend: (id: number) => void; + moveTo: (x: number, y: number) => void; + updateCritter: () => unknown; // Unsure of what this is, maybe settings? + updateGear: (gear: Array) => void; + getShop: () => ShopData; + buyItem: (itemId: string) => void; + trigger: () => void; +} \ No newline at end of file diff --git a/utils.ts b/utils.ts new file mode 100644 index 0000000..aba890a --- /dev/null +++ b/utils.ts @@ -0,0 +1,167 @@ +import { Image } from 'https://deno.land/x/imagescript@1.3.0/mod.ts'; +import chalk from "https://deno.land/x/chalk_deno@v4.1.1-deno/source/index.js" +import { join } from "https://deno.land/std@0.224.0/path/mod.ts"; + +import { rooms, spawnRoom } from "./constants/world.ts"; +import { Room, LocalPlayer, PlayerCrumb } from "./types.ts"; + +/** Condenses the local player variable into data that is sufficient enough for other clients */ +export function makeCrumb(player: LocalPlayer, roomId: string): PlayerCrumb { + return { + i: player.playerId, + n: player.nickname, + c: player.critterId, + x: player.x, + y: player.y, + r: player.rotation, + g: player.gear, + + // message & emote + m: "", + e: "", + + _roomId: roomId + } +} + +// TODO: use the correct triggers for the active party +export async function getTrigger(player: LocalPlayer, roomId: string, partyId: string) { + const room = rooms[roomId][partyId]; + if (!room) { + console.log(chalk.red(`[!] Cannot find room: "${roomId}@${partyId}"!`)); + return; + } + + try { + //@ts-ignore: Deno lint + const treasureBuffer = await Deno.readFile(room.media.treasure?.replace('..','public')); + const treasure = await Image.decode(treasureBuffer); + if (!treasure) throw new Error('Missing map server for room "' + roomId + '"!'); + + const pixel = treasure.getPixelAt(player.x, player.y); + const r = (pixel >> 24) & 0xFF, g = (pixel >> 16) & 0xFF, b = (pixel >> 8) & 0xFF; + const hexCode = ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0'); + + const trigger = room.triggers.find((trigger) => trigger.hex == hexCode); + if (trigger) { + return trigger.server; + } else { + return null; + } + } catch(e) { + console.warn(chalk.red('[!] Caught error while checking for activated trigger.'), e); + } +} + +export function getNewCodeItem(player: LocalPlayer, items: Array) { + const itemsSet = new Set(player.inventory); + const available = items.filter(item => !itemsSet.has(item)); + return available.length === 0 ? null : available[Math.floor(Math.random() * available.length)]; +} + +/** + * Indexes the /media/rooms directory for all versions of all rooms + * @returns All versions of every room + */ +export async function indexRoomData() { + const _roomData: Record> = {}; + + const basePath = join(Deno.cwd(), 'public', 'media', 'rooms'); + const _rooms = Deno.readDir(basePath); + + for await (const room of _rooms) { + if (room.isDirectory) { + _roomData[room.name] = {}; + const roomPath = join(basePath, room.name); + const versions = Deno.readDir(roomPath); + for await (const version of versions) { + if (version.isDirectory) { + const versionPath = join(roomPath, version.name, 'data.json'); + try { + const data = await Deno.readTextFile(versionPath); + _roomData[room.name][version.name] = JSON.parse(data); + } catch(_) { + console.log(chalk.red('[!] "%s@%s" is missing a data.json file'), room.name, version.name); + }; + } + } + } + } + + return _roomData +} + +export async function getAccount(nickname?: string) { + let accounts = []; + try { + const data = await Deno.readTextFile('accounts.json'); + accounts = JSON.parse(data); + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + console.log(chalk.gray('Persistent login JSON is missing, using blank JSON array..')); + accounts = []; + } else { + console.log(chalk.red('[!] Failure to fetch persistent login data with nickname: '), nickname); + throw error; + }; + } + + if (nickname) { + const existingAccount = accounts.find((player: { nickname: string }) => player.nickname == nickname); + if (existingAccount) { + return { + all: accounts, + individual: existingAccount, + } + } else { + return { + all: accounts, + individual: null + } + } + } else { + return accounts; + } +} + +export async function updateAccount(nickname: string, property: string, value: unknown) { + if (["x", "y", "rotation", "_partyId"].includes(property)) return; + const accounts = await getAccount(nickname); + + accounts.individual[property] = value; + await Deno.writeTextFile('accounts.json', JSON.stringify(accounts.all, null, 2)); +} + +export function trimAccount(player: LocalPlayer) { + for (const key of [ + "critterId", + "x", + "y", + "rotation", + "_partyId", + "_mods" + ]) { + delete player[key]; + } + return player; +} + +export function expandAccount(player: LocalPlayer) { + const defaultPos = rooms[spawnRoom].default; + player.x = defaultPos.startX; + player.y = defaultPos.startY; + player.rotation = defaultPos.startR; + return player; +} + +export function getDirection(x: number, y: number, targetX: number, targetY: number) { + const a = Math.floor((180 * Math.atan2(targetX - x, y - targetY)) / Math.PI); + return a < 0 ? a + 360 : a; +} + +export async function createAccount(player: LocalPlayer) { + const accounts = await getAccount(); + accounts.push(trimAccount(player)); + + await Deno.writeTextFile('accounts.json', JSON.stringify(accounts, null, 2)); +} \ No newline at end of file