diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..10d1919 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: debug + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: "write" + contents: "read" + steps: + - uses: actions/checkout@v5 + - uses: cachix/install-nix-action@v31 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + - run: nix build + # - run: nix flake check + - name: Upload static site artifact + uses: actions/upload-artifact@v7 + with: + name: site + path: result/static + # - name: Run `nix shell` + # run: nix develop --command -- bunx wrangler pages deploy result/static + # env: + # CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_PAGES_API_KEY }} + # CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18be870 --- /dev/null +++ b/.gitignore @@ -0,0 +1,122 @@ +# Ignore build outputs from performing a nix-build or `nix build` command +result +result-* + +# Ignore automatically generated direnv output +.direnv + +out/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# database +*.sqlite + +# package-json +package-json.lock + +# bun deploy file +node_modules.bun + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..f32bd6a --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,141 @@ +{ + "plugins": ["unicorn", "typescript", "oxc"], + "categories": { + "correctness": "error", + "suspicious": "error" + }, + "rules": { + "for-direction": "warn", + "no-async-promise-executor": "warn", + "no-caller": "warn", + "no-class-assign": "warn", + "no-compare-neg-zero": "warn", + "no-cond-assign": "warn", + "no-const-assign": "warn", + "no-constant-binary-expression": "warn", + "no-constant-condition": "warn", + "no-control-regex": "warn", + "no-debugger": "warn", + "no-delete-var": "warn", + "no-dupe-class-members": "warn", + "no-dupe-else-if": "warn", + "no-dupe-keys": "warn", + "no-duplicate-case": "warn", + "no-empty-character-class": "warn", + "no-empty-pattern": "warn", + "no-empty-static-block": "warn", + "no-eval": "warn", + "no-ex-assign": "warn", + "no-extra-boolean-cast": "warn", + "no-func-assign": "warn", + "no-global-assign": "warn", + "no-import-assign": "warn", + "no-invalid-regexp": "warn", + "no-irregular-whitespace": "warn", + "no-loss-of-precision": "warn", + "no-new-native-nonconstructor": "warn", + "no-nonoctal-decimal-escape": "warn", + "no-obj-calls": "warn", + "no-self-assign": "warn", + "no-setter-return": "warn", + "no-shadow-restricted-names": "warn", + "no-sparse-arrays": "warn", + "no-this-before-super": "warn", + "no-unassigned-vars": "warn", + "no-unsafe-finally": "warn", + "no-unsafe-negation": "warn", + "no-unsafe-optional-chaining": "warn", + "no-unused-labels": "warn", + "no-unused-private-class-members": "warn", + "no-unused-vars": "warn", + "no-useless-backreference": "warn", + "no-useless-catch": "warn", + "no-useless-escape": "warn", + "no-useless-rename": "warn", + "no-with": "warn", + "require-yield": "warn", + "use-isnan": "warn", + "valid-typeof": "warn", + "oxc/bad-array-method-on-arguments": "warn", + "oxc/bad-char-at-comparison": "warn", + "oxc/bad-comparison-sequence": "warn", + "oxc/bad-min-max-func": "warn", + "oxc/bad-object-literal-comparison": "warn", + "oxc/bad-replace-all-arg": "warn", + "oxc/const-comparisons": "warn", + "oxc/double-comparisons": "warn", + "oxc/erasing-op": "warn", + "oxc/missing-throw": "warn", + "oxc/number-arg-out-of-range": "warn", + "oxc/only-used-in-recursion": "warn", + "oxc/uninvoked-array-callback": "warn", + "typescript/await-thenable": "warn", + "typescript/no-array-delete": "warn", + "typescript/no-base-to-string": "warn", + "typescript/no-confusing-void-expression": "warn", + "typescript/no-duplicate-enum-values": "warn", + "typescript/no-duplicate-type-constituents": "warn", + "typescript/no-extra-non-null-assertion": "warn", + "typescript/no-floating-promises": "warn", + "typescript/no-for-in-array": "warn", + "typescript/no-implied-eval": "warn", + "typescript/no-meaningless-void-operator": "warn", + "typescript/no-misused-new": "warn", + "typescript/no-misused-spread": "warn", + "typescript/no-non-null-asserted-optional-chain": "warn", + "typescript/no-redundant-type-constituents": "warn", + "typescript/no-this-alias": "warn", + "typescript/no-unnecessary-parameter-property-assignment": "warn", + "typescript/no-unsafe-declaration-merging": "warn", + "typescript/no-unsafe-unary-minus": "warn", + "typescript/no-useless-empty-export": "warn", + "typescript/no-wrapper-object-types": "warn", + "typescript/prefer-as-const": "warn", + "typescript/require-array-sort-compare": "warn", + "typescript/restrict-template-expressions": "warn", + "typescript/triple-slash-reference": "warn", + "typescript/unbound-method": "warn", + "unicorn/no-await-in-promise-methods": "warn", + "unicorn/no-empty-file": "warn", + "unicorn/no-invalid-fetch-options": "warn", + "unicorn/no-invalid-remove-event-listener": "warn", + "unicorn/no-new-array": "warn", + "unicorn/no-single-promise-in-promise-methods": "warn", + "unicorn/no-thenable": "warn", + "unicorn/no-unnecessary-await": "warn", + "unicorn/no-useless-fallback-in-spread": "warn", + "unicorn/no-useless-length-check": "warn", + "unicorn/no-useless-spread": "warn", + "unicorn/prefer-set-size": "warn", + "unicorn/prefer-string-starts-ends-with": "warn" + }, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {}, + "attributes": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + } + }, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": ["metadata.js"] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..764c1dd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/better-custom-hotkeys.user.js b/better-custom-hotkeys.user.js deleted file mode 100644 index 98bf2b7..0000000 --- a/better-custom-hotkeys.user.js +++ /dev/null @@ -1,163 +0,0 @@ - -// ==UserScript== -// @name Better hotkeys -// @namespace Violentmonkey Scripts -// @match https://flatmmo.com/play.php* -// @grant GM_getValue -// @grant GM_setValue -// @version 0.001 -// @author Joshu -// @description set the default f key shortcuts to regular keys, sets enter to toggle chat focus istead of always listening. -// @inject-into page -// ==/UserScript== - -// User Defined HotKeys - Customize via ViolentMonkey storage (GM_getValue/GM_setValue) -const HOTKEY_CONFIG = { - [GM_getValue('hotkey_worship', '1')]: { - originalKey: 'F1', - description: 'Worship - Activates the Run worship ability' - }, - [GM_getValue('hotkey_consumeFood', '2')]: { - originalKey: 'F2', - description: 'Inventory - Consumes a piece of food' - }, - [GM_getValue('hotkey_lightFire', '3')]: { - originalKey: 'F3', - description: 'Inventory - Lights a fire' - }, - [GM_getValue('hotkey_autoEquip1', 'q')]: { - originalKey: 'F6', - description: 'Equipment - Auto equips items that you\'ve configured' - }, - [GM_getValue('hotkey_autoEquip2', 'w')]: { - originalKey: 'F7', - description: 'Equipment - Auto equips items that you\'ve configured' - }, - [GM_getValue('hotkey_autoEquip3', 'e')]: { - originalKey: 'F8', - description: 'Equipment - Auto equips items that you\'ve configured' - }, - [GM_getValue('hotkey_badge1', 'a')]: { - originalKey: 'F9', - description: 'Badge - Right click a badge and click the \'set key binding\'' - }, - [GM_getValue('hotkey_badge2', 's')]: { - originalKey: 'F10', - description: 'Badge - Right click a badge and click the \'set key binding\'' - }, - [GM_getValue('hotkey_badge3', 'd')]: { - originalKey: 'F11', - description: 'Badge - Right click a badge and click the \'set key binding\'' - }, - [GM_getValue('hotkey_badge4', 'f')]: { - originalKey: 'F12', - description: 'Badge - Right click a badge and click the \'set key binding\'' - } -}; - - -//can be from canvas or chat input -const focusOrSendChat = () => { - const value = chat_ele.value.trim(); - - if (document.activeElement !== chat_ele) { - request_focus_chatbox(); - return; - } - - if(value !== "") { - Globals.websocket.send('CHAT=' + value); - chat_ele.value = ""; - } - request_unfocus_chatbox(); -} - -// Just leaving this section the same as the OG as much as possible -const handleNpcChatModal = (e) => { - let keyCode = e.keyCode; - if(has_npc_chat_message_modal_open()) { - if(keyCode == 32) { - document.getElementById("npc-chat-message-modal-continue-btn").click(); - e.preventDefault(); - } - return; - } - if(has_npc_chat_options_modal_open()) { - switch(keyCode) { - case 49: { - let wrapper = document.getElementById("npc-chat-options-modal-content"); - let options = wrapper.getElementsByTagName("div"); - if(options[0].style.display != 'none') { - options[0].click(); - } - } - break; - case 50: - { - let wrapper = document.getElementById("npc-chat-options-modal-content"); - let options = wrapper.getElementsByTagName("div"); - if(options[1].style.display != 'none') { - options[1].click(); - } - } - - break; - case 51: - { - let wrapper = document.getElementById("npc-chat-options-modal-content"); - let options = wrapper.getElementsByTagName("div"); - if(options[2].style.display != 'none') { - options[2].click(); - } - } - - break; - case 52: - { - let wrapper = document.getElementById("npc-chat-options-modal-content"); - let options = wrapper.getElementsByTagName("div"); - if(options[3].style.display != 'none') { - options[3].click(); - } - } - break; - } - } -} - -const hotkeyListener = (e) => { - if(e.repeat) return; - // Checks from original handler - if(Globals.local_username == null) return; - - if(has_npc_chat_message_modal_open()) { - handleNpcChatModal(e); - return; - } - - if(has_modal_open()) return; - - if (e.key === "Enter") { - focusOrSendChat(); - e.preventDefault(); - } - - if(document.activeElement.id != "body") { - return; - } - - if (e.key === "/") { - chat_ele.value = '/'; - request_focus_chatbox(); - e.preventDefault(); - } - - if (e.key in HOTKEY_CONFIG) { - const pressedHotkey = HOTKEY_CONFIG[e.key]; - Globals.websocket.send(`SHORTCUT_KEY=${pressedHotkey.originalKey}`) - e.preventDefault(); - } -} - -window.removeEventListener("keypress", keypress_listener) -window.addEventListener("keydown", hotkeyListener, false); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..ba74412 --- /dev/null +++ b/biome.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..db8e4dc --- /dev/null +++ b/bun.lock @@ -0,0 +1,273 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "flatmmo-userscripts-workspace", + "dependencies": { + "effect": "^3.19.14", + "oxc-parser": "^0.108.0", + }, + "devDependencies": { + "@types/bun": "^1.3.4", + "@violentmonkey/types": "^0.3.1", + "bun-plugin-solid": "^1.0.0", + "flatmmo-types": "workspace:*", + "typescript": "^5.9.3", + }, + }, + "packages/lib/flatmmo-types": { + "name": "flatmmo-types", + "version": "0.0.1", + }, + "packages/userscripts/chat-space-fix": { + "name": "chat-space-fix", + "version": "1.0.0", + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*", + }, + }, + "packages/userscripts/dev-tools": { + "name": "dev-tools", + "version": "1.0.0", + "dependencies": { + "solid-js": "^1.9.5", + }, + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "bun-plugin-solid": "^1.0.0", + "flatmmo-types": "workspace:*", + }, + }, + "packages/userscripts/hud-improved": { + "name": "hud-improved", + "version": "1.0.0", + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*", + }, + }, + "packages/userscripts/keybinds-improved": { + "name": "keybinds-improved", + "version": "1.0.0", + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + + "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="], + + "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.108.0", "", { "os": "android", "cpu": "arm" }, "sha512-TemaHZYErFqspRHfsGb1dvWICigOciP4xKlcBVvO8znkHzdJGWbtPwqQc5f1cfrdFynctXIzRQh3qizzQJMqpg=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.108.0", "", { "os": "android", "cpu": "arm64" }, "sha512-dnsD8uS2FaqpTv+AKCN3iVSRiWvR0PsrSqJCy4Z4+E4YLTpD0Ui9IYFSeu1rZVxwJUnCXs4j56dvZ4LO2NUv7A=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.108.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EPKhTey/qzezNIRot95CUMLGmK6sn9bDWgdfWSYCUXv9AwJJNjc6klRf3bslQTN35fyFXz/EM6Z9gD45YXcj0g=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.108.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vhBov136GRb03/v0FmcgC2voAfNv89vOeUs5I64yvWqc3EFI9/Ja2jp1+982RGp6+wfVUKSDTymUOkAS8STGDA=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.108.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TbFhRqPzck2IGmJBrD/+SWGIv3NaimcAZySmi9dqG7aiMyhbt/XOMVith6cmuoSWcZDrEJiBr7RNQ/aW7YhsDQ=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.108.0", "", { "os": "linux", "cpu": "arm" }, "sha512-X8BRbNGAM8t4oLmgp9mANZhN/rLcBledr33BT9BpCJwcIjZbPxPlQHmj/JuM4Ww6wYNeWWpeZCCKp4uDQHE06A=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.108.0", "", { "os": "linux", "cpu": "arm" }, "sha512-FtQwtAg/N/LqqSwSNsWu1TEywOETQwHXuHDYzIpa3DwvRPWYTlqLta6OPaXfAxqj5Iiy95GiZPf+dOb77mn5gQ=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.108.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+oP0UaHHIKx/VtREFuB/m0EyZKKHt+8/LUL9hSdqakNByD9CFg2i1Fu98x+pAOPYn0C8WiTzJLoaClhLBJYKdA=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.108.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-s45g0+UDL7fR9z+WewuA0YEwZWJrpL+Nma4Xay20TGMnYnUsl7H5E0te+1ovLIeBMVK6CKUFg1A0AUqxaSagmg=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.108.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-h4ExnKtDzlZy577LxVL0N7XOyyn+UUqmSCzz/HPk3RwPEEL1YXZFPEcW1tYqXZnSZmBfmGxw9svSeTJlH37czw=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.108.0", "", { "os": "linux", "cpu": "none" }, "sha512-gi7fGSz6tiR8BIkN6JaQ0vjH6QGfAXB402lyKS6y5RcEBDnfrBAymYyjZJ3bQPV5rbev7YD/KzY0hJrNDQqUmQ=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.108.0", "", { "os": "linux", "cpu": "none" }, "sha512-c44OgBdt3FE9P+OyboIYObg/2eejsor90wQM6A8fEZzi2CCJJzPeXEQDsdVIJBpSla5b2HkDeB5FJpEHyhMZyw=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.108.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-MMPAQSxaHwsDNMOc3b+RIcqSnQWWOVYCFl2nuOvz2OxA1MHA2xvkB2w8B6QxnM5r4ikA6d/yydpvOynNugnJoA=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.108.0", "", { "os": "linux", "cpu": "x64" }, "sha512-uao/CmiUkSrfIF4WIT0c/mh+PqwIzux43/Q8NZvJrn4KLZaKR8veGPsJjUBN5igJerh+I5XJ4tjtYbFJV1So+A=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.108.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5TTi3ohehU+VLTbq3g8XYpyrjsmQHaaicOvVgOh+3QGWm3x0JLsYZ+Pa23oHj++dBbYnhYKB+O1KwOoNIibtVg=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.108.0", "", { "os": "none", "cpu": "arm64" }, "sha512-YTNJnBoyVSNdGG9xmvfi8mzLDbHqlOKHrYzGvORFPFImBj3VYfQXEYJ2v2rp5eQ8B+wffPg+8+IE2GQLLQ2B2A=="], + + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.108.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-zzalhAJT9PA6HdXtzfcDOvP9nXN/vBLswPtPBR7bFTsjowIcclHSBi8aj34pbhDABcmJeFPdwe+o2o4Midv25Q=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.108.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-VZwk6B8MYzPrhrQLlfkMb3lXhLvOxf/mR5BG299EgqlHIu7/NS/KNCoX7cXtDdlghNmChfu/G6vEdjYOyyERgg=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.108.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-pjZlfs7k41Ui/tL9ibOn1wA3b6/ow7ugQBSPU8etQnUv5qTB+TxZLQvxtV0lJI4wVRSUuwM709ngrjctFtix0g=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.108.0", "", { "os": "win32", "cpu": "x64" }, "sha512-3yUPthhhF6BelQKfuOiyFO5/MRYmuSc2fK6if/ft84D1sp8lX7hvuDl+/90owmls1N4rGGS/Jp9R5YlJXzQ8nQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.108.0", "", {}, "sha512-7lf13b2IA/kZO6xgnIZA88sq3vwrxWk+2vxf6cc+omwYCRTiA5e63Beqf3fz/v8jEviChWWmFYBwzfSeyrsj7Q=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + + "@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="], + + "@violentmonkey/types": ["@violentmonkey/types@0.3.2", "", { "dependencies": { "user-agent-data-types": "^0.4.2" } }, "sha512-SclmkQqyN22upcCsNYoRDh3Tco7YRGXHBThWOSl09YA3fk7DxSi1sKDfm0uVQc+g8wrjfykVxN3Ws7Jh+5ccIg=="], + + "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.3", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w=="], + + "babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "bun-plugin-solid": ["bun-plugin-solid@1.0.0", "", { "dependencies": { "@babel/core": ">=7.0.0", "@babel/preset-typescript": ">=7.0.0", "babel-preset-solid": ">=1.8.0" } }, "sha512-H4ZcW4afCLYLDtBsog5cvCfQo8f5E7SD8AeT1woVmIKBOMguy/ml9f1jz9UlikN/i+VK1bS6GII46ZR2OdhbBQ=="], + + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="], + + "chat-space-fix": ["chat-space-fix@workspace:packages/userscripts/chat-space-fix"], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "dev-tools": ["dev-tools@workspace:packages/userscripts/dev-tools"], + + "effect": ["effect@3.19.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-vzMmgfZKLcojmUjBdlQx+uaKryO7yULlRxjpDnHdnvcp1NPHxJyoM6IOXBLlzz2I/uPtZpGKavt5hBv7IvGZkA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.278", "", {}, "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="], + + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + + "flatmmo-types": ["flatmmo-types@workspace:packages/lib/flatmmo-types"], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="], + + "hud-improved": ["hud-improved@workspace:packages/userscripts/hud-improved"], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keybinds-improved": ["keybinds-improved@workspace:packages/userscripts/keybinds-improved"], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + + "oxc-parser": ["oxc-parser@0.108.0", "", { "dependencies": { "@oxc-project/types": "^0.108.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.108.0", "@oxc-parser/binding-android-arm64": "0.108.0", "@oxc-parser/binding-darwin-arm64": "0.108.0", "@oxc-parser/binding-darwin-x64": "0.108.0", "@oxc-parser/binding-freebsd-x64": "0.108.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.108.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.108.0", "@oxc-parser/binding-linux-arm64-gnu": "0.108.0", "@oxc-parser/binding-linux-arm64-musl": "0.108.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.108.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.108.0", "@oxc-parser/binding-linux-riscv64-musl": "0.108.0", "@oxc-parser/binding-linux-s390x-gnu": "0.108.0", "@oxc-parser/binding-linux-x64-gnu": "0.108.0", "@oxc-parser/binding-linux-x64-musl": "0.108.0", "@oxc-parser/binding-openharmony-arm64": "0.108.0", "@oxc-parser/binding-wasm32-wasi": "0.108.0", "@oxc-parser/binding-win32-arm64-msvc": "0.108.0", "@oxc-parser/binding-win32-ia32-msvc": "0.108.0", "@oxc-parser/binding-win32-x64-msvc": "0.108.0" } }, "sha512-eM0GUxQgVZXZxB364HRlakUH8rBxh5E6dN+RiiCLtOk84WgLFbhydULyd2DUJYxguvcbjWUmmKgVDyvVCeplDA=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="], + + "seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="], + + "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "user-agent-data-types": ["user-agent-data-types@0.4.2", "", {}, "sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], + } +} diff --git a/bun.nix b/bun.nix new file mode 100644 index 0000000..c753980 --- /dev/null +++ b/bun.nix @@ -0,0 +1,425 @@ +# Autogenerated by `bun2nix`, editing manually is not recommended +# +# Set of Bun packages to install +# +# Consume this with `fetchBunDeps` (recommended) +# or `pkgs.callPackage` if you wish to handle +# it manually. +{ + copyPathToStore, + fetchFromGitHub, + fetchgit, + fetchurl, + ... +}: +{ + "@babel/code-frame@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz"; + hash = "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="; + }; + "@babel/compat-data@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz"; + hash = "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="; + }; + "@babel/core@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz"; + hash = "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="; + }; + "@babel/generator@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz"; + hash = "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="; + }; + "@babel/helper-annotate-as-pure@7.27.3" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz"; + hash = "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="; + }; + "@babel/helper-compilation-targets@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz"; + hash = "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="; + }; + "@babel/helper-create-class-features-plugin@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz"; + hash = "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="; + }; + "@babel/helper-globals@7.28.0" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz"; + hash = "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="; + }; + "@babel/helper-member-expression-to-functions@7.28.5" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz"; + hash = "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="; + }; + "@babel/helper-module-imports@7.18.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz"; + hash = "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="; + }; + "@babel/helper-module-imports@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz"; + hash = "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="; + }; + "@babel/helper-module-transforms@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz"; + hash = "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="; + }; + "@babel/helper-optimise-call-expression@7.27.1" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz"; + hash = "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="; + }; + "@babel/helper-plugin-utils@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz"; + hash = "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="; + }; + "@babel/helper-replace-supers@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz"; + hash = "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="; + }; + "@babel/helper-skip-transparent-expression-wrappers@7.27.1" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz"; + hash = "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="; + }; + "@babel/helper-string-parser@7.27.1" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"; + hash = "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="; + }; + "@babel/helper-validator-identifier@7.28.5" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz"; + hash = "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="; + }; + "@babel/helper-validator-option@7.27.1" = fetchurl { + url = "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz"; + hash = "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="; + }; + "@babel/helpers@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz"; + hash = "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="; + }; + "@babel/parser@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz"; + hash = "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="; + }; + "@babel/plugin-syntax-jsx@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz"; + hash = "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="; + }; + "@babel/plugin-syntax-typescript@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz"; + hash = "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="; + }; + "@babel/plugin-transform-modules-commonjs@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz"; + hash = "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="; + }; + "@babel/plugin-transform-typescript@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz"; + hash = "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="; + }; + "@babel/preset-typescript@7.28.5" = fetchurl { + url = "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz"; + hash = "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="; + }; + "@babel/template@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz"; + hash = "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="; + }; + "@babel/traverse@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz"; + hash = "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="; + }; + "@babel/types@7.28.6" = fetchurl { + url = "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz"; + hash = "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="; + }; + "@emnapi/core@1.8.1" = fetchurl { + url = "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz"; + hash = "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="; + }; + "@emnapi/runtime@1.8.1" = fetchurl { + url = "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz"; + hash = "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="; + }; + "@emnapi/wasi-threads@1.1.0" = fetchurl { + url = "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz"; + hash = "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="; + }; + "@jridgewell/gen-mapping@0.3.13" = fetchurl { + url = "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz"; + hash = "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="; + }; + "@jridgewell/remapping@2.3.5" = fetchurl { + url = "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz"; + hash = "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="; + }; + "@jridgewell/resolve-uri@3.1.2" = fetchurl { + url = "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"; + hash = "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="; + }; + "@jridgewell/sourcemap-codec@1.5.5" = fetchurl { + url = "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"; + hash = "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="; + }; + "@jridgewell/trace-mapping@0.3.31" = fetchurl { + url = "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz"; + hash = "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="; + }; + "@napi-rs/wasm-runtime@1.1.1" = fetchurl { + url = "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz"; + hash = "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="; + }; + "@oxc-parser/binding-android-arm-eabi@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.108.0.tgz"; + hash = "sha512-TemaHZYErFqspRHfsGb1dvWICigOciP4xKlcBVvO8znkHzdJGWbtPwqQc5f1cfrdFynctXIzRQh3qizzQJMqpg=="; + }; + "@oxc-parser/binding-android-arm64@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.108.0.tgz"; + hash = "sha512-dnsD8uS2FaqpTv+AKCN3iVSRiWvR0PsrSqJCy4Z4+E4YLTpD0Ui9IYFSeu1rZVxwJUnCXs4j56dvZ4LO2NUv7A=="; + }; + "@oxc-parser/binding-darwin-arm64@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.108.0.tgz"; + hash = "sha512-EPKhTey/qzezNIRot95CUMLGmK6sn9bDWgdfWSYCUXv9AwJJNjc6klRf3bslQTN35fyFXz/EM6Z9gD45YXcj0g=="; + }; + "@oxc-parser/binding-darwin-x64@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.108.0.tgz"; + hash = "sha512-vhBov136GRb03/v0FmcgC2voAfNv89vOeUs5I64yvWqc3EFI9/Ja2jp1+982RGp6+wfVUKSDTymUOkAS8STGDA=="; + }; + "@oxc-parser/binding-freebsd-x64@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.108.0.tgz"; + hash = "sha512-TbFhRqPzck2IGmJBrD/+SWGIv3NaimcAZySmi9dqG7aiMyhbt/XOMVith6cmuoSWcZDrEJiBr7RNQ/aW7YhsDQ=="; + }; + "@oxc-parser/binding-linux-arm-gnueabihf@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.108.0.tgz"; + hash = "sha512-X8BRbNGAM8t4oLmgp9mANZhN/rLcBledr33BT9BpCJwcIjZbPxPlQHmj/JuM4Ww6wYNeWWpeZCCKp4uDQHE06A=="; + }; + "@oxc-parser/binding-linux-arm-musleabihf@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.108.0.tgz"; + hash = "sha512-FtQwtAg/N/LqqSwSNsWu1TEywOETQwHXuHDYzIpa3DwvRPWYTlqLta6OPaXfAxqj5Iiy95GiZPf+dOb77mn5gQ=="; + }; + "@oxc-parser/binding-linux-arm64-gnu@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.108.0.tgz"; + hash = "sha512-+oP0UaHHIKx/VtREFuB/m0EyZKKHt+8/LUL9hSdqakNByD9CFg2i1Fu98x+pAOPYn0C8WiTzJLoaClhLBJYKdA=="; + }; + "@oxc-parser/binding-linux-arm64-musl@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.108.0.tgz"; + hash = "sha512-s45g0+UDL7fR9z+WewuA0YEwZWJrpL+Nma4Xay20TGMnYnUsl7H5E0te+1ovLIeBMVK6CKUFg1A0AUqxaSagmg=="; + }; + "@oxc-parser/binding-linux-ppc64-gnu@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.108.0.tgz"; + hash = "sha512-h4ExnKtDzlZy577LxVL0N7XOyyn+UUqmSCzz/HPk3RwPEEL1YXZFPEcW1tYqXZnSZmBfmGxw9svSeTJlH37czw=="; + }; + "@oxc-parser/binding-linux-riscv64-gnu@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.108.0.tgz"; + hash = "sha512-gi7fGSz6tiR8BIkN6JaQ0vjH6QGfAXB402lyKS6y5RcEBDnfrBAymYyjZJ3bQPV5rbev7YD/KzY0hJrNDQqUmQ=="; + }; + "@oxc-parser/binding-linux-riscv64-musl@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.108.0.tgz"; + hash = "sha512-c44OgBdt3FE9P+OyboIYObg/2eejsor90wQM6A8fEZzi2CCJJzPeXEQDsdVIJBpSla5b2HkDeB5FJpEHyhMZyw=="; + }; + "@oxc-parser/binding-linux-s390x-gnu@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.108.0.tgz"; + hash = "sha512-MMPAQSxaHwsDNMOc3b+RIcqSnQWWOVYCFl2nuOvz2OxA1MHA2xvkB2w8B6QxnM5r4ikA6d/yydpvOynNugnJoA=="; + }; + "@oxc-parser/binding-linux-x64-gnu@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.108.0.tgz"; + hash = "sha512-uao/CmiUkSrfIF4WIT0c/mh+PqwIzux43/Q8NZvJrn4KLZaKR8veGPsJjUBN5igJerh+I5XJ4tjtYbFJV1So+A=="; + }; + "@oxc-parser/binding-linux-x64-musl@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.108.0.tgz"; + hash = "sha512-5TTi3ohehU+VLTbq3g8XYpyrjsmQHaaicOvVgOh+3QGWm3x0JLsYZ+Pa23oHj++dBbYnhYKB+O1KwOoNIibtVg=="; + }; + "@oxc-parser/binding-openharmony-arm64@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.108.0.tgz"; + hash = "sha512-YTNJnBoyVSNdGG9xmvfi8mzLDbHqlOKHrYzGvORFPFImBj3VYfQXEYJ2v2rp5eQ8B+wffPg+8+IE2GQLLQ2B2A=="; + }; + "@oxc-parser/binding-wasm32-wasi@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.108.0.tgz"; + hash = "sha512-zzalhAJT9PA6HdXtzfcDOvP9nXN/vBLswPtPBR7bFTsjowIcclHSBi8aj34pbhDABcmJeFPdwe+o2o4Midv25Q=="; + }; + "@oxc-parser/binding-win32-arm64-msvc@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.108.0.tgz"; + hash = "sha512-VZwk6B8MYzPrhrQLlfkMb3lXhLvOxf/mR5BG299EgqlHIu7/NS/KNCoX7cXtDdlghNmChfu/G6vEdjYOyyERgg=="; + }; + "@oxc-parser/binding-win32-ia32-msvc@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.108.0.tgz"; + hash = "sha512-pjZlfs7k41Ui/tL9ibOn1wA3b6/ow7ugQBSPU8etQnUv5qTB+TxZLQvxtV0lJI4wVRSUuwM709ngrjctFtix0g=="; + }; + "@oxc-parser/binding-win32-x64-msvc@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.108.0.tgz"; + hash = "sha512-3yUPthhhF6BelQKfuOiyFO5/MRYmuSc2fK6if/ft84D1sp8lX7hvuDl+/90owmls1N4rGGS/Jp9R5YlJXzQ8nQ=="; + }; + "@oxc-project/types@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/@oxc-project/types/-/types-0.108.0.tgz"; + hash = "sha512-7lf13b2IA/kZO6xgnIZA88sq3vwrxWk+2vxf6cc+omwYCRTiA5e63Beqf3fz/v8jEviChWWmFYBwzfSeyrsj7Q=="; + }; + "@standard-schema/spec@1.1.0" = fetchurl { + url = "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz"; + hash = "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="; + }; + "@tybys/wasm-util@0.10.1" = fetchurl { + url = "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz"; + hash = "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="; + }; + "@types/bun@1.3.6" = fetchurl { + url = "https://registry.npmjs.org/@types/bun/-/bun-1.3.6.tgz"; + hash = "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="; + }; + "@types/node@25.0.10" = fetchurl { + url = "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz"; + hash = "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="; + }; + "@violentmonkey/types@0.3.2" = fetchurl { + url = "https://registry.npmjs.org/@violentmonkey/types/-/types-0.3.2.tgz"; + hash = "sha512-SclmkQqyN22upcCsNYoRDh3Tco7YRGXHBThWOSl09YA3fk7DxSi1sKDfm0uVQc+g8wrjfykVxN3Ws7Jh+5ccIg=="; + }; + "babel-plugin-jsx-dom-expressions@0.40.3" = fetchurl { + url = "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.3.tgz"; + hash = "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w=="; + }; + "babel-preset-solid@1.9.10" = fetchurl { + url = "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz"; + hash = "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="; + }; + "baseline-browser-mapping@2.9.18" = fetchurl { + url = "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz"; + hash = "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="; + }; + "browserslist@4.28.1" = fetchurl { + url = "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz"; + hash = "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="; + }; + "bun-plugin-solid@1.0.0" = fetchurl { + url = "https://registry.npmjs.org/bun-plugin-solid/-/bun-plugin-solid-1.0.0.tgz"; + hash = "sha512-H4ZcW4afCLYLDtBsog5cvCfQo8f5E7SD8AeT1woVmIKBOMguy/ml9f1jz9UlikN/i+VK1bS6GII46ZR2OdhbBQ=="; + }; + "bun-types@1.3.6" = fetchurl { + url = "https://registry.npmjs.org/bun-types/-/bun-types-1.3.6.tgz"; + hash = "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="; + }; + "caniuse-lite@1.0.30001766" = fetchurl { + url = "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz"; + hash = "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="; + }; + "chat-space-fix" = copyPathToStore ./packages/userscripts/chat-space-fix; + "convert-source-map@2.0.0" = fetchurl { + url = "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"; + hash = "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="; + }; + "csstype@3.2.3" = fetchurl { + url = "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"; + hash = "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="; + }; + "debug@4.4.3" = fetchurl { + url = "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"; + hash = "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="; + }; + "dev-tools" = copyPathToStore ./packages/userscripts/dev-tools; + "effect@3.19.15" = fetchurl { + url = "https://registry.npmjs.org/effect/-/effect-3.19.15.tgz"; + hash = "sha512-vzMmgfZKLcojmUjBdlQx+uaKryO7yULlRxjpDnHdnvcp1NPHxJyoM6IOXBLlzz2I/uPtZpGKavt5hBv7IvGZkA=="; + }; + "electron-to-chromium@1.5.278" = fetchurl { + url = "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz"; + hash = "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="; + }; + "entities@6.0.1" = fetchurl { + url = "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz"; + hash = "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="; + }; + "escalade@3.2.0" = fetchurl { + url = "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"; + hash = "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="; + }; + "fast-check@3.23.2" = fetchurl { + url = "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz"; + hash = "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="; + }; + "flatmmo-types" = copyPathToStore ./packages/lib/flatmmo-types; + "gensync@1.0.0-beta.2" = fetchurl { + url = "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"; + hash = "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="; + }; + "html-entities@2.3.3" = fetchurl { + url = "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz"; + hash = "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="; + }; + "hud-improved" = copyPathToStore ./packages/userscripts/hud-improved; + "js-tokens@4.0.0" = fetchurl { + url = "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"; + hash = "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="; + }; + "jsesc@3.1.0" = fetchurl { + url = "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"; + hash = "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="; + }; + "json5@2.2.3" = fetchurl { + url = "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"; + hash = "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="; + }; + "keybinds-improved" = copyPathToStore ./packages/userscripts/keybinds-improved; + "lru-cache@5.1.1" = fetchurl { + url = "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"; + hash = "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="; + }; + "ms@2.1.3" = fetchurl { + url = "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"; + hash = "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="; + }; + "node-releases@2.0.27" = fetchurl { + url = "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz"; + hash = "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="; + }; + "oxc-parser@0.108.0" = fetchurl { + url = "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.108.0.tgz"; + hash = "sha512-eM0GUxQgVZXZxB364HRlakUH8rBxh5E6dN+RiiCLtOk84WgLFbhydULyd2DUJYxguvcbjWUmmKgVDyvVCeplDA=="; + }; + "parse5@7.3.0" = fetchurl { + url = "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz"; + hash = "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="; + }; + "picocolors@1.1.1" = fetchurl { + url = "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"; + hash = "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="; + }; + "pure-rand@6.1.0" = fetchurl { + url = "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz"; + hash = "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="; + }; + "semver@6.3.1" = fetchurl { + url = "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"; + hash = "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="; + }; + "seroval-plugins@1.5.0" = fetchurl { + url = "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.0.tgz"; + hash = "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="; + }; + "seroval@1.5.0" = fetchurl { + url = "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz"; + hash = "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="; + }; + "solid-js@1.9.11" = fetchurl { + url = "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz"; + hash = "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="; + }; + "tslib@2.8.1" = fetchurl { + url = "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"; + hash = "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="; + }; + "typescript@5.9.3" = fetchurl { + url = "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"; + hash = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="; + }; + "undici-types@7.16.0" = fetchurl { + url = "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz"; + hash = "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="; + }; + "update-browserslist-db@1.2.3" = fetchurl { + url = "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz"; + hash = "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="; + }; + "user-agent-data-types@0.4.2" = fetchurl { + url = "https://registry.npmjs.org/user-agent-data-types/-/user-agent-data-types-0.4.2.tgz"; + hash = "sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw=="; + }; + "yallist@3.1.1" = fetchurl { + url = "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"; + hash = "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="; + }; +} \ No newline at end of file diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..641e878 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install] +linker = "isolated" diff --git a/draw-player-on-top.user.js b/draw-player-on-top.user.js deleted file mode 100644 index d6198be..0000000 --- a/draw-player-on-top.user.js +++ /dev/null @@ -1,252 +0,0 @@ -// ==UserScript== -// @name Draw your player on top -// @namespace Violentmonkey Scripts -// @match https://flatmmo.com/play.php* -// @grant none -// @version 1.0 -// @author - -// @description 11/30/2025, 12:44:06 PM -// @inject-into page -// ==/UserScript== - -// Function to paint player extracted from loop in original paint_players function. -function paint_player(username) { - if (players.hasOwnProperty(username)) { - - - let client_x = players[username].client_x; - let client_y = players[username].client_y; - if(players[username].client_pathing != null && players[username].client_pathing.length > 0) { - - - let client_next_x_y = players[username].client_pathing[0]; - let client_next_x = client_next_x_y.x; - let client_next_y = client_next_x_y.y; - let speed = 2; - if(players[username].is_running) { - speed = 4; - } - - let moving = false; - if(client_x != client_next_x) { - if(client_next_x > client_x) { - moving = true; - players[username].client_x +=speed; - if(client_next_x < players[username].client_x) { - players[username].client_x = client_next_x; - } - } else if(client_next_x < client_x) { - moving = true; - players[username].client_x -=speed; - if(client_next_x > players[username].client_x) { - players[username].client_x = client_next_x; - } - } - } - - if(client_y != client_next_y) { - - if(client_next_y > client_y) { - moving = true; - players[username].client_y +=speed; - if(client_next_y < players[username].client_y) { - players[username].client_y = client_next_y; - } - } else if(client_next_y < client_y) { - moving = true; - players[username].client_y -=speed; - if(client_next_y > players[username].client_y) { - players[username].client_y = client_next_y; - } - } - } - if(!moving) { - players[username].client_pathing.shift(); - } - } - let flip = players[username].face_left; - - if(player_paint_dims.has(username)) { - ctx.globalAlpha = 0.2; - } - let body_animation = get_player_animation(username, 'body') - if(body_animation != null) { - let img = body_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - - let necklace_animation = get_player_animation(username, 'necklace') - if(necklace_animation != null) { - let img = necklace_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - - let head_animation = get_player_animation(username, 'head') - if(head_animation != null) { - let img = head_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - - - let hair_animation = get_player_animation(username, 'hair') - if(hair_animation != null && (get_equipment(username, 'head') == 'none' || get_equipment(username, 'head') == 'dark')) { - let img = hair_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - - let hat_animation = get_player_animation(username, 'hat') - if(hat_animation != null) { - const Y_OFFSET_40_16 = 8; // ONLY FOR HATS!!! - let img = hat_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE - Y_OFFSET_40_16); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE - Y_OFFSET_40_16); - } - } - - let legs_animation = get_player_animation(username, 'legs') - if(legs_animation != null) { - let img = legs_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - let boots_animation = get_player_animation(username, 'boots') - if(boots_animation != null) { - let img = boots_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - let gloves_animation = get_player_animation(username, 'gloves') - if(gloves_animation != null) { - let img = gloves_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - let weapon_animation = get_player_animation(username, 'weapon') - if(weapon_animation != null) { - let img = weapon_animation.get_frame() - if(flip) { - ctx.scale(-1, 1); - ctx.drawImage(img, -1 * (players[username].client_x + TILE_SIZE + (TILE_SIZE / 2)), players[username].client_y - TILE_SIZE); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - else { - ctx.drawImage(img, players[username].client_x - TILE_SIZE / 2, players[username].client_y - TILE_SIZE); - } - } - - ctx.globalAlpha = 1.0; - - //usernames/levels - ctx.fillStyle = "yellow"; - let text_width = ctx.measureText(username).width; - ctx.fillText(username, client_x + TILE_SIZE / 2 - text_width/2, client_y + TILE_SIZE + TILE_SIZE/4); - ctx.fillStyle = "white"; - let text_level_width = ctx.measureText("level " + players[username].total_level).width; - if(players[username].total_level != null) { - ctx.fillText("level " + players[username].total_level, client_x + TILE_SIZE / 2 - text_level_width/2, client_y + TILE_SIZE + TILE_SIZE /2); - } - - //quest & ach icons - - if(players[username].has_all_quests && players[username].all_ach_completed) { - ctx.drawImage(quest_img, client_x - text_width / 2, client_y - 8 + (64 + TILE_SIZE / 6)); - ctx.drawImage(ach_img, client_x + 12 - text_width / 2, client_y - 8 + (64 + TILE_SIZE / 6)); - - } else { - if(players[username].has_all_quests) { - ctx.drawImage(quest_img, client_x + 12 - text_width / 2, client_y - 8 + (64 + TILE_SIZE / 6)); - } - if(players[username].all_ach_completed) { - ctx.drawImage(ach_img, client_x + 12 - text_width / 2, client_y - 8 + (64 + TILE_SIZE / 6)); - } - } - - - - //hp bar - if(players[username].hp != null && players[username].in_combat_ticker > 0) { - let max_hp = players[username].max_hp; - let hp = players[username].hp; - let perc = hp / max_hp; - ctx.fillStyle = "red"; - ctx.fillRect(players[username].client_x + 4, players[username].client_y - TILE_SIZE - TILE_SIZE/8, TILE_SIZE - 8, 10); - ctx.fillStyle = "lime"; - ctx.fillRect(players[username].client_x + 4, players[username].client_y - TILE_SIZE - TILE_SIZE/8, (TILE_SIZE - 8) * perc, 10); - players[username].in_combat_ticker--; - } - - - - //shadow - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.globalAlpha = 0.2; - ctx.ellipse(players[username].client_x + TILE_SIZE / 2, players[username].client_y + TILE_SIZE - TILE_SIZE / 8, 25, 14, Math.PI * 2, 0, 2 * Math.PI); - ctx.fill(); - ctx.globalAlpha = 1.0; - } -} - -// Follows code in original function, but skips painting in order to paint last. -function paint_players() { - for (let username in players) { - if(username === Globals.local_username) { - continue; - } - paint_player(username); - } - paint_player(Globals.local_username); -} -window.paint_player = paint_player; -window.paint_players = paint_players; diff --git a/fix-ground-item-monster-click-conflict.user.js b/fix-ground-item-monster-click-conflict.user.js deleted file mode 100644 index cad1bcc..0000000 --- a/fix-ground-item-monster-click-conflict.user.js +++ /dev/null @@ -1,135 +0,0 @@ - -// ==UserScript== -// @name Fix other user item enemy click conflict -// @namespace Violentmonkey Scripts -// @match https://flatmmo.com/play.php* -// @grant none -// @version 0.0000001 -// @author - -// @description Fix issue where you can't click on an enemy if they are also standing on another players drop. -// @inject-into page. Currently just clicks both the enemy and the ground tile if both exist since theres no way to tell if an item is your's before clikcing on it and the update is async from the websocket -// ==/UserScript== - -// Function to paint player extracted from loop in original paint_players function. -function mouse_click_handler_patched(e) { - if(Globals.local_username == null) { - return; - } - let bounding_client_rect = canvas.getBoundingClientRect(); - const relativeX = (e.clientX - bounding_client_rect.left) / canvas_scale; - const relativeY = (e.clientY - bounding_client_rect.top) / canvas_scale; - - let clicked_tile = get_postition_from_pixel(relativeX, relativeY); - - //dev tool - if(tile_marker_mode) { - for(let i = 0; i < tiles_marked.length; i++) { - let tile = tiles_marked[i]; - if(tile.x == clicked_tile.x && tile.y == clicked_tile.y) { - tiles_marked.splice(i, 1) - return; - } - } - - tiles_marked.push({x: clicked_tile.x, y: clicked_tile.y}); - return; - } - - //highlight players - for (let username in players) { - if (players.hasOwnProperty(username)) { - let player = players[username]; - if(is_mouse_on_player(mouse_over_now.x, mouse_over_now.y, player)) { - if(e.which == 3) { - Globals.websocket.send('RIGHT_CLICKED_PLAYER=' + username); - return; - } - } - } - } - - for(let i = 0; i < ground_items.length; i++) { - let ground_item = ground_items[i]; - if(is_mouse_on_ground_item(clicked_tile.x, clicked_tile.y, ground_item)) { - Globals.websocket.send('CLICKED_GROUND_ITEM=' + ground_item.uuid); - activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); - // only break if going to click npc and not right click. kinda jank but want to minimize the diff. is_hidden means the npc is dead.... - if(Object.values(npcs).some((n) => !n.is_hidden && is_mouse_on_npc(mouse_over_now.x, mouse_over_now.y, n)) && e.which !== 3) { - break; - } - return; - } - } - - let npcs_clicked_uuid = []; - for (let uuid in npcs) { - if (npcs.hasOwnProperty(uuid)) { - let npc = npcs[uuid]; - if(npc.is_hidden) { - continue; - } - // npc.is_mouse_hovering_over() -- changed for mobile fix. - - if(is_mouse_on_npc(mouse_over_now.x, mouse_over_now.y, npc)) { - - if(e.which == 3) { - Globals.websocket.send('MONSTER_LOG=' + npc.name); - return; - } - if(npc.has_click_priority) { - npcs_clicked_uuid.unshift(npc.uuid); - } else { - npcs_clicked_uuid.push(npc.uuid); - } - } - } - } - - - if(npcs_clicked_uuid.length > 0) { - send_unrepeatable_bytes_1s('CLICKS_NPC=' + npcs_clicked_uuid[0]); - activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); - return; - } - - for(let i = 0; i < map_objects.length; i++) { - let map_object = map_objects[i]; - if(is_mouse_on_map_object(clicked_tile.x, clicked_tile.y, map_object)) { - if(map_object.is_interactable()) { - if(e.which == 3) { - Globals.websocket.send('RIGHT_CLICKED_MAP_OBJECT=' + map_object.uuid); - return; - } else { - send_unrepeatable_bytes_1s('CLICKED_MAP_OBJECT=' + map_object.uuid); - activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); - return; - } - - } - } - } - - - - - let clicked_teleport_tile_flag = false; - for(let i = 0; i < teleport_tiles.length; i++) { - let teleport_tile = teleport_tiles[i]; - - if(teleport_tile.x == clicked_tile.x && teleport_tile.y == clicked_tile.y) { - clicked_teleport_tile_flag = true; - break; - } - } - if(clicked_teleport_tile_flag) { - activate_click_animation("blue", mouse_over_now.x, mouse_over_now.y); - } else { - activate_click_animation("yellow", mouse_over_now.x, mouse_over_now.y); - } - - send_unrepeatable_bytes('CLICKED_TILE=' + clicked_tile.x + "~" + clicked_tile.y); -} - -// replace with patched listener -canvas.removeEventListener("mousedown", window.mouse_click_handler); -canvas.addEventListener("mousedown", mouse_click_handler_patched); diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..4e2cda6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,208 @@ +{ + "nodes": { + "bun2nix": { + "inputs": { + "flake-parts": "flake-parts", + "import-tree": "import-tree", + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1770895533, + "narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=", + "owner": "nix-community", + "repo": "bun2nix", + "rev": "c843f477b15f51151f8c6bcc886954699440a6e1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "bun2nix", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1763759067, + "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "import-tree": { + "locked": { + "lastModified": 1763762820, + "narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=", + "owner": "vic", + "repo": "import-tree", + "rev": "3c23749d8013ec6daa1d7255057590e9ca726646", + "type": "github" + }, + "original": { + "owner": "vic", + "repo": "import-tree", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773821835, + "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1761765539, + "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_2": { + "locked": { + "lastModified": 1772328832, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1770107345, + "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "bun2nix": "bun2nix", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs", + "systems": "systems", + "treefmt-nix": "treefmt-nix_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "bun2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762938485, + "narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1773297127, + "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8d35c8b --- /dev/null +++ b/flake.nix @@ -0,0 +1,104 @@ +{ + description = "Bun2Nix workspace sample"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default"; + + bun2nix.url = "github:nix-community/bun2nix"; + bun2nix.inputs.nixpkgs.follows = "nixpkgs"; + bun2nix.inputs.systems.follows = "systems"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + treefmt-nix.url = "github:numtide/treefmt-nix"; + }; + + # Use the cached version of bun2nix from the nix-community cli + nixConfig = { + extra-substituters = [ + "https://cache.nixos.org" + "https://nix-community.cachix.org" + ]; + extra-trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + ]; + }; + + outputs = + inputs@{ + flake-parts, + nixpkgs, + bun2nix, + ... + }: + + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "aarch64-darwin" + ]; + imports = [ + inputs.treefmt-nix.flakeModule + ]; + perSystem = + { + config, + pkgs, + system, + ... + }: + let + # fetch scripts dynamically based on dir location. May need a stricter/more correct check + userscriptNames = builtins.attrNames ( + inputs.nixpkgs.lib.filterAttrs (n: v: v == "directory") (builtins.readDir ./packages/userscripts) + ); + mkFromName = + packageName: + pkgs.callPackage ./mkUserscript.nix { + inherit packageName; + }; + userscriptAttrs = pkgs.lib.genAttrs userscriptNames mkFromName; + in + { + # This sets `pkgs` to a nixpkgs with allowUnfree option set. + _module.args.pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + overlays = [ inputs.bun2nix.overlays.default ]; + }; + + treefmt = { + programs.biome.enable = true; + programs.nixfmt = { + enable = true; + excludes = [ "bun.nix" ]; + }; + }; + + packages = { + # Produce a package for this template with bun2nix in + # the overlay + default = pkgs.callPackage ./mkStaticSite.nix { + userscripts = pkgs.lib.attrValues userscriptAttrs; + }; + } + // userscriptAttrs; + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + bun + # Add the bun2nix binary to our devshell + # Optional now that we have a binary on npm + bun2nix.packages.${system}.default + ]; + + shellHook = '' + export PACKAGESDIR=$(pwd)/packages/ + export USERSCRIPTS=${builtins.concatStringsSep "," userscriptNames} + export BUILD_ENV=dev + bun install --frozen-lockfile + ''; + }; + }; + }; +} diff --git a/header-links.user.js b/header-links.user.js deleted file mode 100644 index c0f5628..0000000 --- a/header-links.user.js +++ /dev/null @@ -1,54 +0,0 @@ -// ==UserScript== -// @name FlatMMO Wiki Header Links -// @namespace Violentmonkey Scripts -// @match https://flatmmo.wiki/index.php/* -// @grant none -// @version 1.0 -// @author - -// @description Adds anchor links to headers on wiki pages -// ==/UserScript== - -(function() { - 'use strict'; - - // Find all headers (h1-h6) - const headers = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); - - headers.forEach(header => { - // Get text content - const text = header.textContent.trim(); - if (!text) return; - - // Check if header already has an ID, or create one - let id = header.id; - if (!id) { - // Create a URL-safe ID from the text - id = text.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, ''); - header.id = id; - } - - // Skip if header already has a link with this href (avoid duplicates) - if (header.querySelector(`a[href="#${id}"]`)) return; - - // Create anchor link - const link = document.createElement('a'); - link.href = `#${id}`; - link.textContent = '#'; - link.style.marginLeft = '5px'; - link.style.textDecoration = 'none'; - link.style.color = '#999'; - link.style.fontSize = '0.9em'; - link.title = `Link to ${text}`; - - // Add hover effect - link.addEventListener('mouseenter', () => { - link.style.color = '#0645ad'; - }); - link.addEventListener('mouseleave', () => { - link.style.color = '#999'; - }); - - // Add link to header - header.appendChild(link); - }); -})(); diff --git a/image-links.user.js b/image-links.user.js deleted file mode 100644 index 76989ae..0000000 --- a/image-links.user.js +++ /dev/null @@ -1,197 +0,0 @@ -// ==UserScript== -// @name FlatMMO Wiki Image Links Modal -// @namespace Violentmonkey Scripts -// @match https://flatmmo.wiki/index.php/* -// @grant none -// @version 1.0 -// @author - -// @description Shows links to pages that use an image in a modal on hover -// ==/UserScript== - -(function() { - 'use strict'; - - // Cache for fetched link data - const linkCache = {}; - let hideTimeout = null; - - // Get current page path for comparison - const currentPath = window.location.pathname; - - // Cache for excluded page links from navbox - let excludedPageLinks = new Set(); - - // Fetch and cache all page links from the navbox template - async function getExcludedPageLinks() { - if (excludedPageLinks.size != 0) { - return excludedPageLinks; - } - - excludedPageLinks = new Set(['/index.php/Template:Navbox_Enemies']); - - try { - const response = await fetch('/index.php/Template:Navbox_Enemies'); - const html = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - - // Find all links ONLY within mw:File spans in the flat-navbox table - const navboxTable = doc.querySelector('table.flat-navbox'); - if (navboxTable) { - const fileSpans = navboxTable.querySelectorAll('span[typeof="mw:File"]'); - fileSpans.forEach(span => { - const link = span.querySelector('a[href^="/index.php/"]'); - if (link) { - const href = link.getAttribute('href'); - if (href && !href.includes('action=edit') && !href.includes('redlink=1')) { - excludedPageLinks.add(href); - } - } - }); - } - } catch (error) { - console.error('Error fetching navbox template:', error); - } - - return excludedPageLinks; - } - - // Create modal element - const modal = document.createElement('div'); - modal.style.position = 'fixed'; - modal.style.backgroundColor = 'white'; - modal.style.border = '1px solid #a2a9b1'; - modal.style.borderRadius = '4px'; - modal.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)'; - modal.style.padding = '10px'; - modal.style.maxWidth = '300px'; - modal.style.maxHeight = '400px'; - modal.style.overflowY = 'auto'; - modal.style.zIndex = '10000'; - modal.style.display = 'none'; - modal.style.fontSize = '14px'; - document.body.appendChild(modal); - - // Find all image links - const imageLinks = document.querySelectorAll('a:has(> .mw-file-element)'); - - imageLinks.forEach(link => { - - const fileUrl = (() => { - if(link.href.search("File") !== -1) { - return link.href; - } - else { - const filename = link.firstChild.src.split("/").pop().split("-").pop(); - return `/index.php/File:${filename}`; - } - } )(); - - // Set ID on the link for anchor navigation - const urlParts = fileUrl.split("/"); - const filePageName = urlParts[urlParts.length - 1]; - const imageName = decodeURIComponent(filePageName.replace('File:', '')); - const anchor = imageName.replace(/\s+/g, '_').replace(/\.[^.]+$/, ''); - link.id = 'File:' + anchor; - - // Show modal on hover - link.addEventListener('mouseenter', async (e) => { - // Clear any pending hide timeout - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } - - modal.style.display = 'block'; - modal.innerHTML = '
Loading...
'; - - // Position modal near cursor - const rect = link.getBoundingClientRect(); - modal.style.left = (rect.right + 10) + 'px'; - modal.style.top = rect.top + 'px'; - - // Fetch links if not cached - if (!linkCache[fileUrl]) { - try { - // Get excluded pages from navbox - const excludedPages = await getExcludedPageLinks(); - - const response = await fetch(fileUrl); - const html = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const linksContainer = doc.querySelector('ul.mw-imagepage-linkstoimage'); - - if (linksContainer) { - - // Get the image filename from the URL - const urlParts = fileUrl.split('/'); - const filePageName = urlParts[urlParts.length - 1]; - // Extract just the filename (e.g., "File:Example.png" -> "Example.png") - const imageName = decodeURIComponent(filePageName.replace('File:', '')); - - // Create the expected page path from image name - const imageBaseName = imageName.replace(/\.[^.]+$/, '').replace(/_/g, '_'); - const expectedPagePath = '/index.php/' + imageBaseName; - - // Clone the container to modify it - const allLinks = Array.from(linksContainer.querySelectorAll('a')); - - // Check if the navbox template is in the file usage list - const navboxInUsageList = allLinks.some(a => - a.getAttribute('href') === '/index.php/Template:Navbox_Enemies' - ); - // TODO better fitering - const filteredLinks = allLinks.filter(a => { - const shortHref = a.getAttribute("href"); - if (document.URL === a.href) { - return false; - } - if (navboxInUsageList && excludedPageLinks.has(shortHref)) { - return false - } - return true - }); - - const linkToUL = (a) => `
  • ${a.title}
  • ` - // Check if there are any links left - if (filteredLinks.length > 0) { - linkCache[fileUrl] = `
      ${filteredLinks.map(linkToUL).join("")}
    ` - } else { - linkCache[fileUrl] = '
    No other pages use this file
    '; - } - } else { - linkCache[fileUrl] = '
    No links found
    '; - } - } catch (error) { - console.log(error) - linkCache[fileUrl] = '
    Error loading links
    '; - } - } - - // Display cached links - modal.innerHTML = '
    Pages using this file:
    ' + linkCache[fileUrl]; - }); - - link.addEventListener('mouseleave', () => { - // Delay hiding the modal to give time to move mouse to it - hideTimeout = setTimeout(() => { - modal.style.display = 'none'; - }, 300); - }); - }); - - // Keep modal open when hovering over it - modal.addEventListener('mouseenter', () => { - // Clear any pending hide timeout - if (hideTimeout) { - clearTimeout(hideTimeout); - hideTimeout = null; - } - }); - - modal.addEventListener('mouseleave', () => { - // Hide modal when leaving it - modal.style.display = 'none'; - }); -})(); diff --git a/mkStaticSite.nix b/mkStaticSite.nix new file mode 100644 index 0000000..480c956 --- /dev/null +++ b/mkStaticSite.nix @@ -0,0 +1,51 @@ +{ + bun2nix, + stdenv, + userscripts, + lib, + ... +}: +let + inherit (lib.fileset) toSource unions; + pname = "username-index-site"; + version = "0.0.1"; + userscript-metadata = ( + map (x: rec { + filename = "${x.pname}.user.js"; + path = "${x}/share/userscripts/${filename}"; + packageName = x.pname; + }) userscripts + ); +in +stdenv.mkDerivation { + inherit pname version; + + src = toSource { + root = ./.; + fileset = unions [ + ./package.json + ./packages + ./bun.nix + ./bun.lock + ./bunfig.toml + ]; + }; + + nativeBuildInputs = [ + bun2nix.hook + ]; + + bunDeps = bun2nix.fetchBunDeps { + bunNix = ./bun.nix; + }; + + buildPhase = '' + bun packages/static-userscript-index/build-site-cli.ts --data='${builtins.toJSON userscript-metadata}' + ''; + + installPhase = '' + mkdir -p $out/static + + cp -R ./dist/static $out + ''; +} diff --git a/mkUserscript.nix b/mkUserscript.nix new file mode 100644 index 0000000..c6e12b8 --- /dev/null +++ b/mkUserscript.nix @@ -0,0 +1,47 @@ +{ + bun2nix, + stdenv, + packageName, + lib, + ... +}: +let + inherit (lib.fileset) toSource unions; + pname = packageName; + version = "1.0.0"; + build-script = "./packages/build/build-userscript-cli.ts"; +in +stdenv.mkDerivation { + inherit pname version; + + src = toSource { + root = ./.; + fileset = unions [ + ./package.json + ./packages + ./bun.nix + ./bun.lock + ./bunfig.toml + ]; + }; + nativeBuildInputs = [ + bun2nix.hook + ]; + dontRunLifecycleScripts = true; + + bunDeps = bun2nix.fetchBunDeps { + bunNix = ./bun.nix; + }; + + buildPhase = '' + bun ${build-script} ${packageName} + ''; + + bunInstallFlags = "--backend=hardlink"; + + installPhase = '' + mkdir -p $out/share/userscripts + + cp ./dist/userscripts/**.js $out/share/userscripts/ + ''; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9cd597b --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "flatmmo-userscripts-workspace", + "workspaces": [ + "packages/lib/*", + "packages/userscripts/*" + ], + "scripts": { + "postinstall": "bun2nix -o bun.nix", + "build-static": "bun run packages/build/build.ts", + "dev-static": "bun run packages/build/dev-watch.ts", + "build-script": "bun run packages/build/build-userscript-cli.ts", + "dev-script": "bun run packages/build/build-userscript-cli.ts --dev" + }, + "devDependencies": { + "@types/bun": "^1.3.4", + "@violentmonkey/types": "^0.3.1", + "bun-plugin-solid": "^1.0.0", + "flatmmo-types": "workspace:*", + "typescript": "^5.9.3" + }, + "dependencies": { + "effect": "^3.19.14", + "oxc-parser": "^0.108.0" + } +} diff --git a/packages/archive/flatmmo/draw-player-on-top.user.js b/packages/archive/flatmmo/draw-player-on-top.user.js new file mode 100644 index 0000000..3b9331d --- /dev/null +++ b/packages/archive/flatmmo/draw-player-on-top.user.js @@ -0,0 +1,361 @@ +// ==UserScript== +// @name Draw your player on top +// @namespace Violentmonkey Scripts +// @match https://flatmmo.com/play.php* +// @grant none +// @version 1.0 +// @author - +// @description 11/30/2025, 12:44:06 PM +// @inject-into page +// ==/UserScript== + +// Function to paint player extracted from loop in original paint_players function. +function paint_player(username) { + if (Object.hasOwn(players, username)) { + const client_x = players[username].client_x; + const client_y = players[username].client_y; + if ( + players[username].client_pathing != null && + players[username].client_pathing.length > 0 + ) { + const client_next_x_y = players[username].client_pathing[0]; + const client_next_x = client_next_x_y.x; + const client_next_y = client_next_x_y.y; + let speed = 2; + if (players[username].is_running) { + speed = 4; + } + + let moving = false; + if (client_x != client_next_x) { + if (client_next_x > client_x) { + moving = true; + players[username].client_x += speed; + if (client_next_x < players[username].client_x) { + players[username].client_x = client_next_x; + } + } else if (client_next_x < client_x) { + moving = true; + players[username].client_x -= speed; + if (client_next_x > players[username].client_x) { + players[username].client_x = client_next_x; + } + } + } + + if (client_y != client_next_y) { + if (client_next_y > client_y) { + moving = true; + players[username].client_y += speed; + if (client_next_y < players[username].client_y) { + players[username].client_y = client_next_y; + } + } else if (client_next_y < client_y) { + moving = true; + players[username].client_y -= speed; + if (client_next_y > players[username].client_y) { + players[username].client_y = client_next_y; + } + } + } + if (!moving) { + players[username].client_pathing.shift(); + } + } + const flip = players[username].face_left; + + if (player_paint_dims.has(username)) { + ctx.globalAlpha = 0.2; + } + const body_animation = get_player_animation(username, "body"); + if (body_animation != null) { + const img = body_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + + const necklace_animation = get_player_animation(username, "necklace"); + if (necklace_animation != null) { + const img = necklace_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + + const head_animation = get_player_animation(username, "head"); + if (head_animation != null) { + const img = head_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + + const hair_animation = get_player_animation(username, "hair"); + if ( + hair_animation != null && + (get_equipment(username, "head") == "none" || + get_equipment(username, "head") == "dark") + ) { + const img = hair_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + + const hat_animation = get_player_animation(username, "hat"); + if (hat_animation != null) { + const Y_OFFSET_40_16 = 8; // ONLY FOR HATS!!! + const img = hat_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE - Y_OFFSET_40_16, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE - Y_OFFSET_40_16, + ); + } + } + + const legs_animation = get_player_animation(username, "legs"); + if (legs_animation != null) { + const img = legs_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + const boots_animation = get_player_animation(username, "boots"); + if (boots_animation != null) { + const img = boots_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + const gloves_animation = get_player_animation(username, "gloves"); + if (gloves_animation != null) { + const img = gloves_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + const weapon_animation = get_player_animation(username, "weapon"); + if (weapon_animation != null) { + const img = weapon_animation.get_frame(); + if (flip) { + ctx.scale(-1, 1); + ctx.drawImage( + img, + -1 * (players[username].client_x + TILE_SIZE + TILE_SIZE / 2), + players[username].client_y - TILE_SIZE, + ); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } else { + ctx.drawImage( + img, + players[username].client_x - TILE_SIZE / 2, + players[username].client_y - TILE_SIZE, + ); + } + } + + ctx.globalAlpha = 1.0; + + //usernames/levels + ctx.fillStyle = "yellow"; + const text_width = ctx.measureText(username).width; + ctx.fillText( + username, + client_x + TILE_SIZE / 2 - text_width / 2, + client_y + TILE_SIZE + TILE_SIZE / 4, + ); + ctx.fillStyle = "white"; + const text_level_width = ctx.measureText( + "level " + players[username].total_level, + ).width; + if (players[username].total_level != null) { + ctx.fillText( + "level " + players[username].total_level, + client_x + TILE_SIZE / 2 - text_level_width / 2, + client_y + TILE_SIZE + TILE_SIZE / 2, + ); + } + + //quest & ach icons + + if ( + players[username].has_all_quests && + players[username].all_ach_completed + ) { + ctx.drawImage( + quest_img, + client_x - text_width / 2, + client_y - 8 + (64 + TILE_SIZE / 6), + ); + ctx.drawImage( + ach_img, + client_x + 12 - text_width / 2, + client_y - 8 + (64 + TILE_SIZE / 6), + ); + } else { + if (players[username].has_all_quests) { + ctx.drawImage( + quest_img, + client_x + 12 - text_width / 2, + client_y - 8 + (64 + TILE_SIZE / 6), + ); + } + if (players[username].all_ach_completed) { + ctx.drawImage( + ach_img, + client_x + 12 - text_width / 2, + client_y - 8 + (64 + TILE_SIZE / 6), + ); + } + } + + //hp bar + if ( + players[username].hp != null && + players[username].in_combat_ticker > 0 + ) { + const max_hp = players[username].max_hp; + const hp = players[username].hp; + const perc = hp / max_hp; + ctx.fillStyle = "red"; + ctx.fillRect( + players[username].client_x + 4, + players[username].client_y - TILE_SIZE - TILE_SIZE / 8, + TILE_SIZE - 8, + 10, + ); + ctx.fillStyle = "lime"; + ctx.fillRect( + players[username].client_x + 4, + players[username].client_y - TILE_SIZE - TILE_SIZE / 8, + (TILE_SIZE - 8) * perc, + 10, + ); + players[username].in_combat_ticker--; + } + + //shadow + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.globalAlpha = 0.2; + ctx.ellipse( + players[username].client_x + TILE_SIZE / 2, + players[username].client_y + TILE_SIZE - TILE_SIZE / 8, + 25, + 14, + Math.PI * 2, + 0, + 2 * Math.PI, + ); + ctx.fill(); + ctx.globalAlpha = 1.0; + } +} + +// Follows code in original function, but skips painting in order to paint last. +function paint_players() { + for (const username in players) { + if (username === Globals.local_username) { + continue; + } + paint_player(username); + } + paint_player(Globals.local_username); +} +window.paint_player = paint_player; +window.paint_players = paint_players; diff --git a/packages/archive/flatmmo/fix-ground-item-monster-click-conflict.user.js b/packages/archive/flatmmo/fix-ground-item-monster-click-conflict.user.js new file mode 100644 index 0000000..4a9601f --- /dev/null +++ b/packages/archive/flatmmo/fix-ground-item-monster-click-conflict.user.js @@ -0,0 +1,140 @@ +// ==UserScript== +// @name Fix other user item enemy click conflict +// @namespace Violentmonkey Scripts +// @match https://flatmmo.com/play.php* +// @grant none +// @version 0.0000001 +// @author - +// @description Fix issue where you can't click on an enemy if they are also standing on another players drop. +// @inject-into page. Currently just clicks both the enemy and the ground tile if both exist since theres no way to tell if an item is your's before clikcing on it and the update is async from the websocket +// ==/UserScript== + +// Function to paint player extracted from loop in original paint_players function. +function mouse_click_handler_patched(e) { + if (Globals.local_username == null) { + return; + } + const bounding_client_rect = canvas.getBoundingClientRect(); + const relativeX = (e.clientX - bounding_client_rect.left) / canvas_scale; + const relativeY = (e.clientY - bounding_client_rect.top) / canvas_scale; + + const clicked_tile = get_postition_from_pixel(relativeX, relativeY); + + //dev tool + if (tile_marker_mode) { + for (let i = 0; i < tiles_marked.length; i++) { + const tile = tiles_marked[i]; + if (tile.x == clicked_tile.x && tile.y == clicked_tile.y) { + tiles_marked.splice(i, 1); + return; + } + } + + tiles_marked.push({ x: clicked_tile.x, y: clicked_tile.y }); + return; + } + + //highlight players + for (const username in players) { + if (Object.hasOwn(players, username)) { + const player = players[username]; + if (is_mouse_on_player(mouse_over_now.x, mouse_over_now.y, player)) { + if (e.which == 3) { + Globals.websocket.send("RIGHT_CLICKED_PLAYER=" + username); + return; + } + } + } + } + + for (let i = 0; i < ground_items.length; i++) { + const ground_item = ground_items[i]; + if (is_mouse_on_ground_item(clicked_tile.x, clicked_tile.y, ground_item)) { + Globals.websocket.send("CLICKED_GROUND_ITEM=" + ground_item.uuid); + activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); + // only break if going to click npc and not right click. kinda jank but want to minimize the diff. is_hidden means the npc is dead.... + if ( + Object.values(npcs).some( + (n) => + !n.is_hidden && + is_mouse_on_npc(mouse_over_now.x, mouse_over_now.y, n), + ) && + e.which !== 3 + ) { + break; + } + return; + } + } + + const npcs_clicked_uuid = []; + for (const uuid in npcs) { + if (Object.hasOwn(npcs, uuid)) { + const npc = npcs[uuid]; + if (npc.is_hidden) { + continue; + } + // npc.is_mouse_hovering_over() -- changed for mobile fix. + + if (is_mouse_on_npc(mouse_over_now.x, mouse_over_now.y, npc)) { + if (e.which == 3) { + Globals.websocket.send("MONSTER_LOG=" + npc.name); + return; + } + if (npc.has_click_priority) { + npcs_clicked_uuid.unshift(npc.uuid); + } else { + npcs_clicked_uuid.push(npc.uuid); + } + } + } + } + + if (npcs_clicked_uuid.length > 0) { + send_unrepeatable_bytes_1s("CLICKS_NPC=" + npcs_clicked_uuid[0]); + activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); + return; + } + + for (let i = 0; i < map_objects.length; i++) { + const map_object = map_objects[i]; + if (is_mouse_on_map_object(clicked_tile.x, clicked_tile.y, map_object)) { + if (map_object.is_interactable()) { + if (e.which == 3) { + Globals.websocket.send("RIGHT_CLICKED_MAP_OBJECT=" + map_object.uuid); + return; + } else { + send_unrepeatable_bytes_1s("CLICKED_MAP_OBJECT=" + map_object.uuid); + activate_click_animation("red", mouse_over_now.x, mouse_over_now.y); + return; + } + } + } + } + + let clicked_teleport_tile_flag = false; + for (let i = 0; i < teleport_tiles.length; i++) { + const teleport_tile = teleport_tiles[i]; + + if ( + teleport_tile.x == clicked_tile.x && + teleport_tile.y == clicked_tile.y + ) { + clicked_teleport_tile_flag = true; + break; + } + } + if (clicked_teleport_tile_flag) { + activate_click_animation("blue", mouse_over_now.x, mouse_over_now.y); + } else { + activate_click_animation("yellow", mouse_over_now.x, mouse_over_now.y); + } + + send_unrepeatable_bytes( + "CLICKED_TILE=" + clicked_tile.x + "~" + clicked_tile.y, + ); +} + +// replace with patched listener +canvas.removeEventListener("mousedown", window.mouse_click_handler); +canvas.addEventListener("mousedown", mouse_click_handler_patched); diff --git a/packages/archive/wiki/header-links.user.js b/packages/archive/wiki/header-links.user.js new file mode 100644 index 0000000..70882f6 --- /dev/null +++ b/packages/archive/wiki/header-links.user.js @@ -0,0 +1,52 @@ +// ==UserScript== +// @name FlatMMO Wiki Header Links +// @namespace Violentmonkey Scripts +// @match https://flatmmo.wiki/index.php/* +// @grant none +// @version 1.0 +// @author - +// @description Adds anchor links to headers on wiki pages +// ==/UserScript== + +(() => { + // Find all headers (h1-h6) + const headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); + + headers.forEach((header) => { + // Get text content + const text = header.textContent.trim(); + if (!text) return; + + // Check if header already has an ID, or create one + let id = header.id; + if (!id) { + // Create a URL-safe ID from the text + id = text.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, ""); + header.id = id; + } + + // Skip if header already has a link with this href (avoid duplicates) + if (header.querySelector(`a[href="#${id}"]`)) return; + + // Create anchor link + const link = document.createElement("a"); + link.href = `#${id}`; + link.textContent = "#"; + link.style.marginLeft = "5px"; + link.style.textDecoration = "none"; + link.style.color = "#999"; + link.style.fontSize = "0.9em"; + link.title = `Link to ${text}`; + + // Add hover effect + link.addEventListener("mouseenter", () => { + link.style.color = "#0645ad"; + }); + link.addEventListener("mouseleave", () => { + link.style.color = "#999"; + }); + + // Add link to header + header.appendChild(link); + }); +})(); diff --git a/packages/archive/wiki/image-links.user.js b/packages/archive/wiki/image-links.user.js new file mode 100644 index 0000000..b6147f5 --- /dev/null +++ b/packages/archive/wiki/image-links.user.js @@ -0,0 +1,212 @@ +// ==UserScript== +// @name FlatMMO Wiki Image Links Modal +// @namespace Violentmonkey Scripts +// @match https://flatmmo.wiki/index.php/* +// @grant none +// @version 1.0 +// @author - +// @description Shows links to pages that use an image in a modal on hover +// ==/UserScript== + +(() => { + // Cache for fetched link data + const linkCache = {}; + let hideTimeout = null; + + // Get current page path for comparison + const currentPath = window.location.pathname; + + // Cache for excluded page links from navbox + let excludedPageLinks = new Set(); + + // Fetch and cache all page links from the navbox template + async function getExcludedPageLinks() { + if (excludedPageLinks.size != 0) { + return excludedPageLinks; + } + + excludedPageLinks = new Set(["/index.php/Template:Navbox_Enemies"]); + + try { + const response = await fetch("/index.php/Template:Navbox_Enemies"); + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + + // Find all links ONLY within mw:File spans in the flat-navbox table + const navboxTable = doc.querySelector("table.flat-navbox"); + if (navboxTable) { + const fileSpans = navboxTable.querySelectorAll( + 'span[typeof="mw:File"]', + ); + fileSpans.forEach((span) => { + const link = span.querySelector('a[href^="/index.php/"]'); + if (link) { + const href = link.getAttribute("href"); + if ( + href && + !href.includes("action=edit") && + !href.includes("redlink=1") + ) { + excludedPageLinks.add(href); + } + } + }); + } + } catch (error) { + console.error("Error fetching navbox template:", error); + } + + return excludedPageLinks; + } + + // Create modal element + const modal = document.createElement("div"); + modal.style.position = "fixed"; + modal.style.backgroundColor = "white"; + modal.style.border = "1px solid #a2a9b1"; + modal.style.borderRadius = "4px"; + modal.style.boxShadow = "0 2px 8px rgba(0,0,0,0.2)"; + modal.style.padding = "10px"; + modal.style.maxWidth = "300px"; + modal.style.maxHeight = "400px"; + modal.style.overflowY = "auto"; + modal.style.zIndex = "10000"; + modal.style.display = "none"; + modal.style.fontSize = "14px"; + document.body.appendChild(modal); + + // Find all image links + const imageLinks = document.querySelectorAll("a:has(> .mw-file-element)"); + + imageLinks.forEach((link) => { + const fileUrl = (() => { + if (link.href.search("File") !== -1) { + return link.href; + } else { + const filename = link.firstChild.src.split("/").pop().split("-").pop(); + return `/index.php/File:${filename}`; + } + })(); + + // Set ID on the link for anchor navigation + const urlParts = fileUrl.split("/"); + const filePageName = urlParts[urlParts.length - 1]; + const imageName = decodeURIComponent(filePageName.replace("File:", "")); + const anchor = imageName.replace(/\s+/g, "_").replace(/\.[^.]+$/, ""); + link.id = "File:" + anchor; + + // Show modal on hover + link.addEventListener("mouseenter", async (e) => { + // Clear any pending hide timeout + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + + modal.style.display = "block"; + modal.innerHTML = '
    Loading...
    '; + + // Position modal near cursor + const rect = link.getBoundingClientRect(); + modal.style.left = rect.right + 10 + "px"; + modal.style.top = rect.top + "px"; + + // Fetch links if not cached + if (!linkCache[fileUrl]) { + try { + // Get excluded pages from navbox + const excludedPages = await getExcludedPageLinks(); + + const response = await fetch(fileUrl); + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const linksContainer = doc.querySelector( + "ul.mw-imagepage-linkstoimage", + ); + + if (linksContainer) { + // Get the image filename from the URL + const urlParts = fileUrl.split("/"); + const filePageName = urlParts[urlParts.length - 1]; + // Extract just the filename (e.g., "File:Example.png" -> "Example.png") + const imageName = decodeURIComponent( + filePageName.replace("File:", ""), + ); + + // Create the expected page path from image name + const imageBaseName = imageName + .replace(/\.[^.]+$/, "") + .replace(/_/g, "_"); + const expectedPagePath = "/index.php/" + imageBaseName; + + // Clone the container to modify it + const allLinks = Array.from(linksContainer.querySelectorAll("a")); + + // Check if the navbox template is in the file usage list + const navboxInUsageList = allLinks.some( + (a) => + a.getAttribute("href") === "/index.php/Template:Navbox_Enemies", + ); + // TODO better fitering + const filteredLinks = allLinks.filter((a) => { + const shortHref = a.getAttribute("href"); + if (document.URL === a.href) { + return false; + } + if (navboxInUsageList && excludedPageLinks.has(shortHref)) { + return false; + } + return true; + }); + + const linkToUL = (a) => + `
  • ${a.title}
  • `; + // Check if there are any links left + if (filteredLinks.length > 0) { + linkCache[fileUrl] = + `
      ${filteredLinks.map(linkToUL).join("")}
    `; + } else { + linkCache[fileUrl] = + '
    No other pages use this file
    '; + } + } else { + linkCache[fileUrl] = + '
    No links found
    '; + } + } catch (error) { + console.log(error); + linkCache[fileUrl] = + '
    Error loading links
    '; + } + } + + // Display cached links + modal.innerHTML = + '
    Pages using this file:
    ' + + linkCache[fileUrl]; + }); + + link.addEventListener("mouseleave", () => { + // Delay hiding the modal to give time to move mouse to it + hideTimeout = setTimeout(() => { + modal.style.display = "none"; + }, 300); + }); + }); + + // Keep modal open when hovering over it + modal.addEventListener("mouseenter", () => { + // Clear any pending hide timeout + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + }); + + modal.addEventListener("mouseleave", () => { + // Hide modal when leaving it + modal.style.display = "none"; + }); +})(); diff --git a/packages/archive/wiki/table-links.user.js b/packages/archive/wiki/table-links.user.js new file mode 100644 index 0000000..025b693 --- /dev/null +++ b/packages/archive/wiki/table-links.user.js @@ -0,0 +1,58 @@ +// ==UserScript== +// @name FlatMMO Wiki Table Links +// @namespace Violentmonkey Scripts +// @match https://flatmmo.wiki/index.php/* +// @grant none +// @version 1.0 +// @author - +// @description Adds anchor links to wikitable rows based on first column text +// ==/UserScript== + +(() => { + // Find all wikitable tables + const tables = document.querySelectorAll("table.wikitable"); + + tables.forEach((table) => { + const rows = table.querySelectorAll("tbody tr"); + + rows.forEach((row) => { + // Skip header rows (rows with th elements) + if (row.querySelector("th")) return; + + // Get first column + const firstCell = row.querySelector("td"); + if (!firstCell) return; + + // Get text content and create ID + const text = firstCell.textContent.trim(); + if (!text) return; + + // Create a URL-safe ID from the text + const id = text.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, ""); + + // Set ID on the row + row.id = id; + + // Create anchor link + const link = document.createElement("a"); + link.href = `#${id}`; + link.textContent = "#"; + link.style.marginLeft = "5px"; + link.style.textDecoration = "none"; + link.style.color = "#999"; + link.style.fontSize = "0.9em"; + link.title = `Link to ${text}`; + + // Add hover effect + link.addEventListener("mouseenter", () => { + link.style.color = "#0645ad"; + }); + link.addEventListener("mouseleave", () => { + link.style.color = "#999"; + }); + + // Add link to first cell + firstCell.appendChild(link); + }); + }); +})(); diff --git a/packages/build/UserscriptMetadata.ts b/packages/build/UserscriptMetadata.ts new file mode 100644 index 0000000..c212599 --- /dev/null +++ b/packages/build/UserscriptMetadata.ts @@ -0,0 +1,192 @@ +/** + * Violentmonkey userscript metadata + */ + +type LocaleKey = `${"name" | "description"}:${string}`; +export type ViolentmonkeyMetadata = { + /** + * The name of the script (required) + * Can include language variants using suffix (e.g., 'name:zh-CN') + */ + name: string; + + [locales: LocaleKey]: string; + + /** + * Namespace for the script (defaults to empty string if not provided) + * Combination of namespace and name creates unique identifier + */ + namespace?: string; + + /** + * Match patterns to decide when script should execute (recommended over @include) + */ + match?: string[]; + + /** + * Exclude match patterns + */ + "exclude-match"?: string[]; + + /** + * Include patterns (old way, prefer @match) + */ + include?: string[]; + + /** + * Exclude patterns + */ + exclude?: string[]; + + /** + * Script version (required for auto-updates) + * Format: numeric parts joined by dots, optionally followed by letters + * @example '1.0', '1.2a.3' + */ + version?: string; + + /** + * Brief description of the script + */ + description?: string; + + /** + * Icon URL for the script + */ + icon?: string; + + /** + * URLs of scripts to load before this one + */ + require?: string[]; + + /** + * Static resources accessible via GM_getResourceText/GM_getResourceURL + */ + resource?: Array<{ + name: string; + url: string; + }>; + + /** + * When the script should run + */ + "run-at"?: + | "document-start" + | "document-body" + | "document-end" + | "document-idle"; + + /** + * If true, script only runs in top-level document, not nested frames + */ + noframes?: boolean; + + /** + * Special APIs and privileges to grant + * Use 'none' to disable sandbox, or specify individual APIs + * @example ['GM_getValue', 'GM_setValue', 'GM.getValue', 'window.close'] + */ + grant?: string[] | ["none"]; + + /** + * Context to inject the script into + */ + "inject-into"?: "page" | "content" | "auto"; + + /** + * URL where script can be downloaded for updates + */ + downloadURL?: string; + + /** + * URL for support/help (linked via question mark icon) + */ + supportURL?: string; + + /** + * URL for script homepage (linked via home icon) + */ + homepageURL?: string; + + /** + * If true, inject script without wrapper into global scope (since VM2.13.1) + * Disables GM API access, can't be used with @top-level-await + */ + unwrap?: boolean; + + /** + * Enables top-level await in script (since VM2.19.2) + * Can't be used with @unwrap + */ + "top-level-await"?: boolean; +}; + +/** + * Metadata field definitions with their types + */ +export const METADATA_FIELDS = [ + { key: "name", type: "string" }, + { key: "namespace", type: "string" }, + { key: "match", type: "list" }, + { key: "exclude-match", type: "list" }, + { key: "include", type: "list" }, + { key: "exclude", type: "list" }, + { key: "version", type: "string" }, + { key: "description", type: "string" }, + { key: "icon", type: "string" }, + { key: "require", type: "list" }, + { key: "resource", type: "list" }, + { key: "run-at", type: "string" }, + { key: "noframes", type: "boolean" }, + { key: "grant", type: "list" }, + { key: "inject-into", type: "string" }, + { key: "downloadURL", type: "string" }, + { key: "supportURL", type: "string" }, + { key: "homepageURL", type: "string" }, + { key: "unwrap", type: "boolean" }, + { key: "top-level-await", type: "boolean" }, +] as const satisfies { + key: keyof ViolentmonkeyMetadata; + type: "string" | "list" | "boolean"; +}[]; + +const boolToLine = (key: keyof ViolentmonkeyMetadata) => `// @${key}`; + +const toLine = (key: keyof ViolentmonkeyMetadata, value: string) => + `// @${key.padEnd(12)} ${value}`; + +const listToLine = (key: keyof ViolentmonkeyMetadata, values: string[]) => { + return values.map((v) => toLine(key, v)); +}; + +export const generateMetadataString = (metadata: ViolentmonkeyMetadata) => { + const localeLines = Object.keys(metadata) + .filter( + (k): k is LocaleKey => + k.startsWith("name:") || k.startsWith("description:"), + ) + .map((k) => toLine(k, metadata[k])); + const lines = METADATA_FIELDS.filter((f) => metadata[f.key]).flatMap((f) => { + if (f.type === "list") { + // Handle array values + if (f.key === "resource") { + const values = metadata[f.key] ?? []; + const to = values.map(({ name, url }) => `${name} ${url}`); + return listToLine(f.key, to); + } + const values = metadata[f.key] ?? []; + return listToLine(f.key, values); + } else if (f.type === "boolean") { + return boolToLine(f.key); + } else { + const value = metadata[f.key] ?? ""; + return toLine(f.key, value); + } + }); + const allLines = [...lines, ...localeLines]; + return `// ==UserScript== +${allLines.join("\n")} +// ==/UserScript== +`; +}; diff --git a/packages/build/build-userscript-cli.ts b/packages/build/build-userscript-cli.ts new file mode 100644 index 0000000..a6c6d8a --- /dev/null +++ b/packages/build/build-userscript-cli.ts @@ -0,0 +1,56 @@ +import { watch } from "fs"; +import { parseArgs } from "util"; +import { buildScript } from "./build-userscript"; + +const { values, positionals } = parseArgs({ + args: Bun.argv, + strict: true, + options: { + dev: { type: "boolean" }, + }, + positionals: ["scriptName"], + allowPositionals: true, +}); + +const name = positionals[2]; +const dev = values.dev; + +const runBuild = async () => { + const t0 = performance.now(); + try { + const filePath = await buildScript(name); + const url = Bun.pathToFileURL(filePath); + const t1 = performance.now(); + + console.log(""); + console.log(`Wrote ${name} in ${t1 - t0} milliseconds to:`); + console.log(url.href); // "file:///foo/bar.txt" + console.log(url.pathname); // "file:///foo/bar.txt" + console.log(""); + } catch (e) { + console.log(`Error while bundling ${name}:`); + console.log(e); // "TypeError" + } +}; + +await runBuild(); + +if (dev) { + const watchDir = `${import.meta.env.PACKAGESDIR}/userscripts/${name}`; + + const watcher = watch( + watchDir, + { recursive: true }, + async (event, filename) => { + console.log(`Detected ${event} in ${filename}`); + await runBuild(); + }, + ); + + process.on("SIGINT", () => { + // close watcher when Ctrl-C is pressed + watcher.close(); + + process.exit(0); + }); +} diff --git a/packages/build/build-userscript.ts b/packages/build/build-userscript.ts new file mode 100644 index 0000000..cb4994a --- /dev/null +++ b/packages/build/build-userscript.ts @@ -0,0 +1,90 @@ +import { SolidPlugin } from "bun-plugin-solid"; +import { Schema } from "effect"; +import { parseSync, Visitor } from "oxc-parser"; +import { + generateMetadataString, + type ViolentmonkeyMetadata, +} from "./UserscriptMetadata"; + +const PackageJson = Schema.Struct({ + name: Schema.String, + module: Schema.String, +}); +export const buildScript = async (packageName: string) => { + const packagePath = `packages/userscripts/${packageName}`; + const file: unknown = await Bun.file(`${packagePath}/package.json`).json(); + const packageInfo = Schema.decodeUnknownSync(PackageJson)(file); + if (typeof file !== "object") throw new Error("Must have package.json"); + + const bundledScript = await getBundledScript( + `./packages/userscripts/${packageName}/${packageInfo.module}`, + ); + + // Temp auto versioning + const defaultMetadataValues: ViolentmonkeyMetadata = { + name: `FlatMMO ${packageInfo.name}` + .replaceAll("-", " ") + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" "), + namespace: "Joshu FlatMMO Scripts", + grant: parseGrantsCalled(bundledScript), + match: ["https://flatmmo.com/play.php*"], + version: `${Date.now()}${process.env.BUILD_ENV ?? ""}`, + }; + const metadata = generateMetadataString(defaultMetadataValues); + + const finalScript = `${metadata} + +${bundledScript} + `; + + const scriptDest = `./dist/userscripts/${packageName}.user.js`; + await Bun.write(scriptDest, finalScript); + return scriptDest; +}; + +const parseGrantsCalled = (scriptText: string) => { + const parsed = parseSync("userscript.js", scriptText); + + // Visit the AST + const grants = new Set(); + const visitor = new Visitor({ + CallExpression({ callee }) { + const { type } = callee; + if (type === "Identifier") { + const name = callee.name; + if (name.startsWith("GM")) { + grants.add(name); + } + } + if ( + type === "MemberExpression" && + callee.object.type === "Identifier" && + callee.object.name === "GM" + ) { + const { property } = callee; + if (property.type === "Identifier") { + grants.add(`GM.${property.name}`); + } + } + }, + }); + + visitor.visit(parsed.program); + return Array.from(grants.keys()); +}; +const getBundledScript = async (entrypoint: string) => { + const bundled = await Bun.build({ + entrypoints: [entrypoint], + plugins: [SolidPlugin({ generate: "dom", hydratable: false })], + }); + + const css = bundled.outputs.filter((x) => x.path.endsWith(".css")).pop(); + const js = await bundled.outputs[0].text(); + if (css) { + const injectCSS = `const cssString = \`${await css.text()}\`;\nGM.addStyle(cssString);`; + return [js, injectCSS].join("\n\n"); + } + return js; +}; diff --git a/packages/build/build.ts b/packages/build/build.ts new file mode 100644 index 0000000..2c32269 --- /dev/null +++ b/packages/build/build.ts @@ -0,0 +1,22 @@ +import { buildStaticSite } from "../static-userscript-index/build-site"; +import { buildScript } from "./build-userscript"; + +const packageNames = process.env.USERSCRIPTS?.split(",") ?? []; + +const userscriptInfo = packageNames.map((packageName) => ({ + path: `./dist/userscripts/${packageName}.user.js`, + filename: `${packageName}.user.js`, + packageName, +})); + +import { rm } from "node:fs/promises"; + +// Delete a directory and all its contents +await rm("./dist", { recursive: true, force: true }); + +await Promise.all(packageNames.map(buildScript)); + +const time = new Date(); +const result = await buildStaticSite(userscriptInfo); +console.log(time); +console.log(result.success); diff --git a/packages/build/dev-watch.ts b/packages/build/dev-watch.ts new file mode 100644 index 0000000..e37f084 --- /dev/null +++ b/packages/build/dev-watch.ts @@ -0,0 +1,20 @@ +import { $ } from "bun"; +import { watch } from "fs"; + +await $`bun run build-static`; // => "Hello, world!" +const watchDir = import.meta.env.PACKAGESDIR; +const watcher = watch( + watchDir, + { recursive: true }, + async (event, filename) => { + console.log(`Detected ${event} in ${filename}`); + await $`bun run build-static`; // => "Hello, world!" + }, +); + +process.on("SIGINT", () => { + // close watcher when Ctrl-C is pressed + watcher.close(); + + process.exit(0); +}); diff --git a/packages/lib/flatmmo-types/flatmmo.d.ts b/packages/lib/flatmmo-types/flatmmo.d.ts new file mode 100644 index 0000000..2fd2d21 --- /dev/null +++ b/packages/lib/flatmmo-types/flatmmo.d.ts @@ -0,0 +1,65 @@ +// import type { WebSocket, WebSocketHandler } from "bun"; + +declare function request_focus_chatbox(): void; +declare function request_unfocus_chatbox(): void; +declare function has_npc_chat_message_modal_open(): boolean; +declare function has_npc_chat_options_modal_open(): boolean; +declare function has_modal_open(): boolean; +declare function keypress_listener(): void; +declare function keydown_listener(): void; +declare const chat_ele: HTMLInputElement; +declare function server_command( + key: string, + values: unknown, + raw_data: unknown, +): void; + +declare function paint_progress_bar(): void; +declare let progress_bar_active: boolean; +declare let progress_bar_at: number; +declare let progress_bar_target: number; +declare const TILE_SIZE: number; +declare const ctx: CanvasRenderingContext2D; + +// const chat_ele: HTMLInputElement; +// const Globals: {websocket: WebSocket}; +interface Globals { + websocket: WebSocket; + local_username: string; + local_id: string; + tab_active: boolean; + websocket_url: string; +} + +interface player { + username: string; + client_x: number; + client_y: number; + x: number; + y: number; + level: number; + hp: number; +} +interface npc { + name: string; + client_x: number; + client_y: number; + x: number; + y: number; + in_combat: boolean; + hp: number; +} + +interface mouseOver { + x: number; + y: number; + x_tile: number; + y_tile: number; +} + +declare const mouse_over_now: mouseOver; + +declare const players: player[]; +declare const npcs: npc[]; + +declare const Globals: Globals; diff --git a/packages/lib/flatmmo-types/package.json b/packages/lib/flatmmo-types/package.json new file mode 100644 index 0000000..48cc5cc --- /dev/null +++ b/packages/lib/flatmmo-types/package.json @@ -0,0 +1,6 @@ +{ + "name": "flatmmo-types", + "version": "0.0.1", + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/static-userscript-index/build-site-cli.ts b/packages/static-userscript-index/build-site-cli.ts new file mode 100644 index 0000000..ddb40c4 --- /dev/null +++ b/packages/static-userscript-index/build-site-cli.ts @@ -0,0 +1,19 @@ +import { parseArgs } from "util"; +import { buildStaticSite } from "./build-site"; + +const { values } = parseArgs({ + args: Bun.argv, + strict: true, + options: { + data: { type: "string" }, + }, + allowPositionals: true, +}); +if (!values.data) { + throw "must have data"; +} + +const data = JSON.parse(values.data); +console.log(data); + +buildStaticSite(data); diff --git a/packages/static-userscript-index/build-site.ts b/packages/static-userscript-index/build-site.ts new file mode 100644 index 0000000..7ba4cac --- /dev/null +++ b/packages/static-userscript-index/build-site.ts @@ -0,0 +1,36 @@ +import { addScriptLinksRewriter } from "./write-html"; + +export const buildStaticSite = ( + userscriptInfo: { filename: string; packageName: string; path: string }[], +) => + Bun.build({ + entrypoints: [ + "./packages/static-userscript-index/index.html", + ...userscriptInfo.map((us) => us.path), + ], + outdir: "./dist/static", + loader: { ".js": "file" }, + naming: { + chunk: `[name].[ext]`, + asset: `[name].[ext]`, + entry: `[name].[ext]`, + }, + root: ".", + splitting: false, + plugins: [ + { + // A plugin that makes every HTML tag lowercase + name: "add-user-scripts", + setup({ onLoad }) { + onLoad({ filter: /\.html$/ }, async (args) => { + const html = await Bun.file(args.path).text(); + + return { + contents: addScriptLinksRewriter(html, userscriptInfo), + loader: "html", + }; + }); + }, + }, + ], + }); diff --git a/packages/static-userscript-index/favicon.png b/packages/static-userscript-index/favicon.png new file mode 100644 index 0000000..f140c87 Binary files /dev/null and b/packages/static-userscript-index/favicon.png differ diff --git a/packages/static-userscript-index/index.html b/packages/static-userscript-index/index.html new file mode 100644 index 0000000..1859980 --- /dev/null +++ b/packages/static-userscript-index/index.html @@ -0,0 +1,31 @@ + + + + + + Flat Scripts + + + + + + +
    +

    Flatmmo Userscripts

    + +
    +
    + +
    + + + \ No newline at end of file diff --git a/packages/static-userscript-index/write-html.ts b/packages/static-userscript-index/write-html.ts new file mode 100644 index 0000000..b1be55a --- /dev/null +++ b/packages/static-userscript-index/write-html.ts @@ -0,0 +1,25 @@ +export const addScriptLinksRewriter = ( + html: string, + userscriptPaths: { filename: string; packageName: string }[], +) => { + const rewriter = new HTMLRewriter().on("#script-list", { + element(ul) { + // remove placeholder content + ul.setInnerContent(""); + userscriptPaths.forEach((us) => { + ul.append( + ` +
  • + ${us.packageName} +
  • `, + { + html: true, + }, + ); + }); + }, + }); + + const result = rewriter.transform(html); + return result; +}; diff --git a/packages/userscripts/chat-space-fix/index.ts b/packages/userscripts/chat-space-fix/index.ts new file mode 100644 index 0000000..a0f4813 --- /dev/null +++ b/packages/userscripts/chat-space-fix/index.ts @@ -0,0 +1,14 @@ +window.removeEventListener("keydown", keydown_listener); +window.addEventListener( + "keydown", + (e) => { + if (e.key.trim().length !== 0) return; + if (chat_ele.value.trimStart().length !== 0) return; + chat_ele.value = chat_ele.value.trimStart(); + + e.preventDefault(); + e.stopPropagation(); + }, + true, +); +window.addEventListener("keydown", keydown_listener); diff --git a/packages/userscripts/chat-space-fix/metadata.js b/packages/userscripts/chat-space-fix/metadata.js new file mode 100644 index 0000000..ba6c804 --- /dev/null +++ b/packages/userscripts/chat-space-fix/metadata.js @@ -0,0 +1,9 @@ +// ==UserScript== +// @name Chat Leading Space Fix +// @namespace Violentmonkey Scripts +// @match https://flatmmo.com/play.php* +// @version VERSION +// @author Joshu +// @description Fix chat with only space. +// @inject-into page +// ==/UserScript== diff --git a/packages/userscripts/chat-space-fix/package.json b/packages/userscripts/chat-space-fix/package.json new file mode 100644 index 0000000..3418016 --- /dev/null +++ b/packages/userscripts/chat-space-fix/package.json @@ -0,0 +1,10 @@ +{ + "name": "chat-space-fix", + "version": "1.0.0", + "module": "index.ts", + "dependencies": {}, + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*" + } +} diff --git a/packages/userscripts/dev-tools/App.module.css b/packages/userscripts/dev-tools/App.module.css new file mode 100644 index 0000000..076b53e --- /dev/null +++ b/packages/userscripts/dev-tools/App.module.css @@ -0,0 +1,22 @@ +.App { + text-align: start; +} + +.header { + background-color: #282c34; + display: flex; + flex-direction: column; + /* font-size: calc(10px + 2vmin); */ + color: white; +} + +.link { + color: #b318f0; +} + +.columns { + display: flex; + flex-direction: row; + gap: 25px; + padding: 25px; +} diff --git a/packages/userscripts/dev-tools/index.tsx b/packages/userscripts/dev-tools/index.tsx new file mode 100644 index 0000000..d6132f0 --- /dev/null +++ b/packages/userscripts/dev-tools/index.tsx @@ -0,0 +1,160 @@ +/* @refresh reload */ + +import { type Component, createSignal, For } from "solid-js"; +import { render } from "solid-js/web"; +// import cssString from "./App.module.css" with { type: "text" }; +import styles from "./App.module.css"; + +// import 'solid-devtools'; + +// import './index.css'; +const root = document.createElement("div"); +const body = document.getElementById("body"); +body.appendChild(root); +// GM.addStyle(cssString); + +// if (import.meta.env.DEV && !(root instanceof HTMLElement)) { +// throw new Error( +// 'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?', +// ); +// } +const [currPlayers, setPlayers] = createSignal([]); +const [items, setItems] = createSignal([]); +const [currNpcs, setCurrNpcs] = createSignal([]); + +const [progressBarStatus, setCount] = createSignal({ + progress_bar_at, + progress_bar_active, + progress_bar_target, + current_action_uuid, +}); +const [socketEvents, setSocketEvents] = createSignal([]); +const setLatestSocketEvents = (key: string) => { + if (socketEvents().length >= 10) { + setSocketEvents([key, ...socketEvents().slice(0, 10)]); + } else { + setSocketEvents([key, ...socketEvents()]); + } +}; +const webSocketProxy = new Proxy(server_command, { + // wraps + apply(target, thisArg, argArray) { + const [key, values, raw_data] = argArray; + // console.log(target.arguments); + // console.log(key, values, raw_data); + target(key, values, raw_data); + if (key === "PROGRESS_BAR" || key === "PROGRESS_BAR_OFF") { + setCount({ + progress_bar_at, + progress_bar_active, + progress_bar_target, + current_action_uuid, + }); + } + if (key === "ACTION_ID_UPDATED") { + setCount({ + progress_bar_at, + progress_bar_active, + progress_bar_target, + current_action_uuid, + }); + } + setPlayers( + Object.entries(players).map( + ([name, p]) => `${name} hp: ${p.hp ?? "-"} x:${p.x} y:${p.y}`, + ), + ); + setCurrNpcs( + Object.values(npcs).map( + (p) => + `${p.name} hp: ${p.hp} x:${p.x} y:${p.y} in_combat: ${p.in_combat}`, + ), + ); + setLatestSocketEvents(key); + }, +}); + +server_command = webSocketProxy; + +const ProgressTracker: Component = () => { + return ( +
    +
    +

    Status

    +

    + progress_bar_active:{" "} + {progressBarStatus().progress_bar_active.toString()} +

    +
    +
    +

    progress_bar_at: {progressBarStatus().progress_bar_at}

    +
    +
    +

    progress_bar_target: {progressBarStatus().progress_bar_target}

    +
    +
    +

    current_action_id: {progressBarStatus().current_action_uuid}

    +
    +
    + ); +}; + +const SocketChatter: Component = () => { + return ( +
    +

    Recent Socket Messages

    + + {(socket, i) => ( +

    + {i()}: {socket} +

    + )} +
    {" "} +
    + ); +}; +const Players: Component = () => { + return ( +
    +

    Players

    + + {(socket, i) => ( +

    + {i()}: {socket} +

    + )} +
    {" "} +
    + ); +}; +const Npcs: Component = () => { + return ( +
    +

    NPCs

    + + {(socket, i) => ( +

    + {i()}: {socket} +

    + )} +
    {" "} +
    + ); +}; + +const App: Component = () => { + return ( +
    +
    +
    + + + + +
    +
    +
    + ); +}; + +render(() => , root); diff --git a/packages/userscripts/dev-tools/package.json b/packages/userscripts/dev-tools/package.json new file mode 100644 index 0000000..d5b5cc8 --- /dev/null +++ b/packages/userscripts/dev-tools/package.json @@ -0,0 +1,13 @@ +{ + "name": "dev-tools", + "version": "1.0.0", + "module": "index.tsx", + "dependencies": { + "solid-js": "^1.9.5" + }, + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "bun-plugin-solid": "^1.0.0", + "flatmmo-types": "workspace:*" + } +} diff --git a/packages/userscripts/hud-improved/index.ts b/packages/userscripts/hud-improved/index.ts new file mode 100644 index 0000000..fe44cdb --- /dev/null +++ b/packages/userscripts/hud-improved/index.ts @@ -0,0 +1,277 @@ +interface Config { + x: number; + y: number; + height: number; + width: number; + colors: { + activeBackgroundColor: string; + activeForegroundColor: string; + activeStrokeColor: string; + idleBackgroundColor: string; + idleStrokeColor: string; + }; + animation: "smooth" | "default" | "off"; +} +const defaultConfig: Config = { + x: 10, + y: 80, + height: 20, + width: TILE_SIZE * 2, + colors: { + activeBackgroundColor: "black", + activeForegroundColor: "purple", + activeStrokeColor: "green", + idleBackgroundColor: "black", + idleStrokeColor: "red", + }, + animation: "smooth", +}; + +if (GM_getValue("config", null) === null) { + GM_setValue("config", defaultConfig); +} +const config = GM_getValue("config", defaultConfig); + +const handleChangeColor = ( + color: string, + field: keyof (typeof config)["colors"], +) => { + config.colors[field] = color; + GM_setValue(`config`, config); +}; + +const menuModal = ` +
    +

    Progress Bar Colors

    + +
    +

    Active State

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    +

    Idle State

    + +
    +
    + + +
    + +
    + + +
    +
    +
    + +
    +

    Animation Style

    + +
    + +
    + Press Ctrl+Alt+M to toggle +
    +
    +`; + +// Inject the modal into the page +const modalContainer = document.createElement("div"); +modalContainer.innerHTML = menuModal; +document.body.appendChild(modalContainer); + +// Attach event listeners to the color inputs +const activeBackgroundInput = document.getElementById( + "activeBackground", +) as HTMLInputElement; +const activeForegroundInput = document.getElementById( + "activeForeground", +) as HTMLInputElement; +const activeStrokeInput = document.getElementById( + "activeStroke", +) as HTMLInputElement; +const idleBackgroundInput = document.getElementById( + "idleBackground", +) as HTMLInputElement; +const idleStrokeInput = document.getElementById( + "idleStroke", +) as HTMLInputElement; + +activeBackgroundInput?.addEventListener("input", (e) => { + handleChangeColor( + (e.target as HTMLInputElement).value, + "activeBackgroundColor", + ); +}); + +activeForegroundInput?.addEventListener("input", (e) => { + handleChangeColor( + (e.target as HTMLInputElement).value, + "activeForegroundColor", + ); +}); + +activeStrokeInput?.addEventListener("input", (e) => { + handleChangeColor((e.target as HTMLInputElement).value, "activeStrokeColor"); +}); + +idleBackgroundInput?.addEventListener("input", (e) => { + handleChangeColor( + (e.target as HTMLInputElement).value, + "idleBackgroundColor", + ); +}); + +idleStrokeInput?.addEventListener("input", (e) => { + handleChangeColor((e.target as HTMLInputElement).value, "idleStrokeColor"); +}); + +const animationStyleSelect = document.getElementById( + "animationStyle", +) as HTMLSelectElement; + +animationStyleSelect?.addEventListener("change", (e) => { + const value = (e.target as HTMLSelectElement).value as Config["animation"]; + config.animation = value; + GM_setValue("config", config); +}); + +let count = 0; +let prevProgress = 0; +const countTickFraction = (() => { + const tickFrames = 28; + return () => { + const totalFrames = (progress_bar_target + 1) * tickFrames; + if (progress_bar_at <= 0 && prevProgress !== 0) { + count = 0; + } else { + count = count + 1; + } + prevProgress = progress_bar_at; + return Math.min(count / totalFrames, 1); + }; +})(); + +const getPercent = (animationType: Config["animation"]) => { + if (animationType === "smooth") { + return countTickFraction(); + } else if (animationType === "default") { + return progress_bar_at / progress_bar_target; + } else if (animationType === "off") { + return 1; + } +}; + +function paintCustomProgressBar() { + //progress bar + const { x, y, height, width, colors } = config; + if (progress_bar_active) { + const percent = getPercent(config.animation); + ctx.fillStyle = colors.activeBackgroundColor; + ctx.fillRect(x, y - TILE_SIZE / 8, width, height); + ctx.fillStyle = colors.activeForegroundColor; + ctx.fillRect(x, y - TILE_SIZE / 8, width * percent, height); + ctx.strokeStyle = colors.activeStrokeColor; + ctx.strokeRect(x, y - TILE_SIZE / 8, width, height); + } else { + prevProgress = 0; + ctx.fillStyle = colors.idleBackgroundColor; + ctx.fillRect(x, y - TILE_SIZE / 8, width, height); + ctx.strokeStyle = colors.idleStrokeColor; + ctx.strokeRect(x, y - TILE_SIZE / 8, width, height); + } +} + +const paint_progress_bar_proxy = new Proxy(paint_progress_bar, { + // wraps + apply(target, thisArg, argArray) { + paintCustomProgressBar(); + target(); + }, +}); + +window.addEventListener("keydown", moveBar); +window.addEventListener("keyup", setCoordinates); + +unsafeWindow.paint_progress_bar = paint_progress_bar_proxy; + +GM_registerMenuCommand( + "Reset to default settings", + () => { + GM_setValue("config", defaultConfig); + config.colors = defaultConfig.colors; + config.height = defaultConfig.height; + config.width = defaultConfig.width; + config.x = defaultConfig.x; + config.y = defaultConfig.y; + config.animation = defaultConfig.animation; + }, + + {}, +); + +function moveBar(this: Window, ev: KeyboardEvent) { + if (ev.altKey && ev.ctrlKey && ev.key === "z") { + config.x = mouse_over_now.x; + config.y = mouse_over_now.y; + } + if (ev.altKey && ev.ctrlKey && ev.key === "j") { + config.width = config.width * 0.9; + config.height = config.height * 0.9; + } + if (ev.altKey && ev.ctrlKey && ev.key === "k") { + config.width = config.width * 1.1; + config.height = config.height * 1.1; + } + if (ev.altKey && ev.ctrlKey && ev.key === "m") { + const modal = document.getElementById("color-picker-modal"); + if (modal) { + modal.style.display = modal.style.display === "none" ? "block" : "none"; + } + } +} +function setCoordinates(this: Window, ev: KeyboardEvent) { + if (ev.altKey && ev.ctrlKey && ev.key === "z") { + GM_setValue("config", config); + } + if (ev.altKey && ev.ctrlKey && ev.key === "j") { + GM_setValue("config", config); + } + if (ev.altKey && ev.ctrlKey && ev.key === "k") { + GM_setValue("config", config); + } +} diff --git a/packages/userscripts/hud-improved/metadata.js b/packages/userscripts/hud-improved/metadata.js new file mode 100644 index 0000000..4df0f05 --- /dev/null +++ b/packages/userscripts/hud-improved/metadata.js @@ -0,0 +1,12 @@ +// ==UserScript== +// @name Custom Action UI +// @namespace Violentmonkey Scripts +// @match https://flatmmo.com/play.php* +// @grant GM_getValue +// @grant GM_setValue +// @grant GM_registerMenuCommand +// @version VERSION +// @author Joshu +// @description ui tweaks to make it easier to tell at what action your character is doing +// @inject-into page +// ==/UserScript== diff --git a/packages/userscripts/hud-improved/package.json b/packages/userscripts/hud-improved/package.json new file mode 100644 index 0000000..c1b6fa8 --- /dev/null +++ b/packages/userscripts/hud-improved/package.json @@ -0,0 +1,10 @@ +{ + "name": "hud-improved", + "version": "1.0.0", + "module": "index.ts", + "dependencies": {}, + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*" + } +} diff --git a/packages/userscripts/keybinds-improved/ACTIONS.ts b/packages/userscripts/keybinds-improved/ACTIONS.ts new file mode 100644 index 0000000..962900c --- /dev/null +++ b/packages/userscripts/keybinds-improved/ACTIONS.ts @@ -0,0 +1,136 @@ +import type { actions } from "./hotkeys"; + +export const ACTIONS: actions = { + run: { + originalKey: "F1", + description: "Run", + socketCommand: "SHORTCUT_KEY=F1", + }, + eat: { + originalKey: "F2", + description: "Consumes a piece of food", + socketCommand: "SHORTCUT_KEY=F2", + }, + lightFire: { + originalKey: "F3", + description: "Lights a fire", + socketCommand: "SHORTCUT_KEY=F3", + }, + equip1: { + originalKey: "F6", + description: "Equipment Auto equips items that you've configured", + socketCommand: "SHORTCUT_KEY=F6", + }, + equip2: { + originalKey: "F7", + description: "Equipment - Auto equips items that you've configured", + socketCommand: "SHORTCUT_KEY=F7", + }, + equip3: { + originalKey: "F8", + description: "Equipment - Auto equips items that you've configured", + socketCommand: "SHORTCUT_KEY=F8", + }, + badge1: { + originalKey: "F9", + description: "Badge - Right click a badge and click the 'set key binding'", + socketCommand: "SHORTCUT_KEY=F9", + }, + badge2: { + originalKey: "F10", + description: "Badge - Right click a badge and click the 'set key binding'", + socketCommand: "SHORTCUT_KEY=F10", + }, + badge3: { + originalKey: "F11", + description: "Badge - Right click a badge and click the 'set key binding'", + socketCommand: "SHORTCUT_KEY=F11", + }, + badge4: { + originalKey: "F12", + description: "Badge - Right click a badge and click the 'set key binding'", + socketCommand: "SHORTCUT_KEY=F12", + }, + + teleport_everbrook: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_everbrook", + }, + + remote_sell: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=remote_sell", + }, + + dig: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=dig", + }, + + teleport_mysticvale: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_mysticvale", + }, + + timers: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=timers", + }, + + teleport_omboko: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_omboko", + }, + + teleport_dock_haven: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_dock_haven", + }, + + auto_hell_burying: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=auto_hell_burying", + }, + + teleport_jafa_outpost: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_jafa_outpost", + }, + + teleport_frostvale: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=teleport_frostvale", + }, + + hunting_contact: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=hunting_contact", + }, + + mass_pickup: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=mass_pickup", + }, + focus: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=focus", + }, + clarity: { + originalKey: "N/A", + description: "Worship skill, originally no hotkey", + socketCommand: "USE_WORSHIP=clarity", + }, +}; diff --git a/packages/userscripts/keybinds-improved/DEFAULT_HOTKEYS.ts b/packages/userscripts/keybinds-improved/DEFAULT_HOTKEYS.ts new file mode 100644 index 0000000..8101dae --- /dev/null +++ b/packages/userscripts/keybinds-improved/DEFAULT_HOTKEYS.ts @@ -0,0 +1,172 @@ +import type { actions, keypress } from "./hotkeys"; + +export const DEFAULT_HOTKEYS: Record = { + run: { + key: "r", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + eat: { + key: "f", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + lightFire: { + key: "4", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + equip1: { + key: "1", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + equip2: { + key: "2", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + equip3: { + key: "3", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + badge1: { + key: "a", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + badge2: { + key: "s", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + badge3: { + key: "d", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + badge4: { + key: "v", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + teleport_everbrook: { + key: "e", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + remote_sell: { + key: "s", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + dig: { + key: "l", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + teleport_mysticvale: { + key: "m", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + timers: { + key: "0", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + teleport_omboko: { + key: "o", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + teleport_dock_haven: { + key: "d", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + auto_hell_burying: { + key: "b", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + teleport_jafa_outpost: { + key: "j", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + teleport_frostvale: { + key: "f", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + hunting_contact: { + key: "h", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + mass_pickup: { + key: "p", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + focus: { + key: "k", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, + clarity: { + key: "c", + altKey: false, + ctrlKey: true, + metaKey: false, + shiftKey: false, + }, +}; diff --git a/packages/userscripts/keybinds-improved/hotkeys.ts b/packages/userscripts/keybinds-improved/hotkeys.ts new file mode 100644 index 0000000..1e6fa18 --- /dev/null +++ b/packages/userscripts/keybinds-improved/hotkeys.ts @@ -0,0 +1,93 @@ +import { DEFAULT_HOTKEYS } from "./DEFAULT_HOTKEYS"; + +export interface keypress { + key: string; + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; +} + +interface actionProperties { + originalKey: string; + description: string; + socketCommand: string; +} + +export type actions = { + run: actionProperties; + eat: actionProperties; + lightFire: actionProperties; + equip1: actionProperties; + equip2: actionProperties; + equip3: actionProperties; + badge1: actionProperties; + badge2: actionProperties; + badge3: actionProperties; + badge4: actionProperties; + teleport_everbrook: actionProperties; + remote_sell: actionProperties; + dig: actionProperties; + teleport_mysticvale: actionProperties; + timers: actionProperties; + teleport_omboko: actionProperties; + teleport_dock_haven: actionProperties; + auto_hell_burying: actionProperties; + teleport_jafa_outpost: actionProperties; + teleport_frostvale: actionProperties; + hunting_contact: actionProperties; + mass_pickup: actionProperties; + focus: actionProperties; + clarity: actionProperties; +}; + +export type HotkeyMap = Record; + +export interface Hotkey { + action: keyof actions; + hotkey: keypress; +} + +export const keypressToHashableString = (keypress: keypress) => { + return `${keypress.key}-${keypress.altKey}-${keypress.ctrlKey}-${keypress.metaKey}-${keypress.shiftKey}`.toLowerCase(); +}; + +if (GM_getValue("hotkeys", null) === null) { + GM_setValue("hotkeys", {}); +} + +let usersHotkeys: Partial = GM_getValue("hotkeys", {}); + +export const mergeHotkeys = (): HotkeyMap => { + return { ...DEFAULT_HOTKEYS, ...usersHotkeys }; +}; + +const hashKeymap = () => { + return Object.entries(mergeHotkeys()).reduce>( + (result, [action, kp]) => { + const hashed = keypressToHashableString(kp); + result[hashed] = { action: action as keyof actions, hotkey: kp }; + return result; + }, + {}, + ); +}; + +export let hashedHotkeyMap = hashKeymap(); + +export const setHotkeys = (updatedHotkeys: Partial) => { + usersHotkeys = { ...usersHotkeys, ...updatedHotkeys }; + GM.setValue("hotkeys", usersHotkeys); + hashedHotkeyMap = hashKeymap(); +}; + +export const hashableStringToKeypress = (str: string): keypress => { + const [key, altKey, ctrlKey, metaKey, shiftKey] = str.split("-"); + return { + key, + altKey: altKey === "true", + ctrlKey: ctrlKey === "true", + metaKey: metaKey === "true", + shiftKey: shiftKey === "true", + }; +}; diff --git a/packages/userscripts/keybinds-improved/index.ts b/packages/userscripts/keybinds-improved/index.ts new file mode 100644 index 0000000..63b709c --- /dev/null +++ b/packages/userscripts/keybinds-improved/index.ts @@ -0,0 +1,124 @@ +import { ACTIONS } from "./ACTIONS"; +import { hashedHotkeyMap, keypressToHashableString } from "./hotkeys"; +import { toggleModal } from "./settings"; + +//can be from canvas or chat input +const focusOrSendChat = () => { + const value = chat_ele.value.trim(); + + if (document.activeElement !== chat_ele) { + request_focus_chatbox(); + return; + } + + if (value !== "") { + Globals.websocket.send(`CHAT=${value}`); + chat_ele.value = ""; + } + request_unfocus_chatbox(); +}; + +// Just leaving this section the same as the OG as much as possible +const handleNpcChatModal = (e: KeyboardEvent) => { + const keyCode = e.keyCode; + if (has_npc_chat_message_modal_open()) { + if (keyCode === 32) { + document.getElementById("npc-chat-message-modal-continue-btn")?.click(); + e.preventDefault(); + } + return; + } + if (has_npc_chat_options_modal_open()) { + switch (keyCode) { + case 49: + { + const wrapper = document.getElementById( + "npc-chat-options-modal-content", + ); + const options = wrapper?.getElementsByTagName("div"); + if (options && options[0].style.display !== "none") { + options[0].click(); + } + } + break; + case 50: + { + const wrapper = document.getElementById( + "npc-chat-options-modal-content", + ); + const options = wrapper?.getElementsByTagName("div"); + if (options && options[1].style.display !== "none") { + options[1].click(); + } + } + + break; + case 51: + { + const wrapper = document.getElementById( + "npc-chat-options-modal-content", + ); + const options = wrapper?.getElementsByTagName("div"); + if (options && options[2].style.display !== "none") { + options[2].click(); + } + } + + break; + case 52: + { + const wrapper = document.getElementById( + "npc-chat-options-modal-content", + ); + const options = wrapper?.getElementsByTagName("div"); + if (options && options[3].style.display !== "none") { + options[3].click(); + } + } + break; + } + } +}; + +const hotkeyListener = (e: KeyboardEvent) => { + if (e.repeat) return; + // Checks from original handler + if (Globals.local_username == null) return; + + if (has_npc_chat_message_modal_open()) { + handleNpcChatModal(e); + return; + } + + if (has_modal_open()) return; + + if (e.key === "Enter") { + focusOrSendChat(); + e.preventDefault(); + } + + if (document.activeElement?.id !== "body") { + return; + } + + if (e.key === "/") { + chat_ele.value = "/"; + request_focus_chatbox(); + e.preventDefault(); + } + + const keypressString = keypressToHashableString(e); + + if (keypressString in hashedHotkeyMap) { + const pressedHotkey = hashedHotkeyMap[keypressString]; + const action = ACTIONS[pressedHotkey.action]; + if (action) { + Globals.websocket.send(action.socketCommand); + e.preventDefault(); + } + } +}; + +GM.registerMenuCommand("Toggle Settings menu", toggleModal); +window.removeEventListener("keypress", keypress_listener); +window.addEventListener("keydown", hotkeyListener, false); diff --git a/packages/userscripts/keybinds-improved/metadata.js b/packages/userscripts/keybinds-improved/metadata.js new file mode 100644 index 0000000..ffd85d9 --- /dev/null +++ b/packages/userscripts/keybinds-improved/metadata.js @@ -0,0 +1,11 @@ +// ==UserScript== +// @name Better hotkeys +// @namespace Violentmonkey Scripts +// @match https://flatmmo.com/play.php* +// @grant GM_getValue +// @grant GM_setValue +// @version VERSION +// @author Joshu +// @description set the default f key shortcuts to regular keys, sets enter to toggle chat focus istead of always listening. +// @inject-into page +// ==/UserScript== diff --git a/packages/userscripts/keybinds-improved/package.json b/packages/userscripts/keybinds-improved/package.json new file mode 100644 index 0000000..af9861e --- /dev/null +++ b/packages/userscripts/keybinds-improved/package.json @@ -0,0 +1,10 @@ +{ + "name": "keybinds-improved", + "version": "1.0.0", + "module": "index.ts", + "dependencies": {}, + "devDependencies": { + "@violentmonkey/types": "^0.3.1", + "flatmmo-types": "workspace:*" + } +} diff --git a/packages/userscripts/keybinds-improved/settings.ts b/packages/userscripts/keybinds-improved/settings.ts new file mode 100644 index 0000000..6eb84a7 --- /dev/null +++ b/packages/userscripts/keybinds-improved/settings.ts @@ -0,0 +1,540 @@ +import { ACTIONS } from "./ACTIONS"; +import { + type actions, + type keypress, + keypressToHashableString, + mergeHotkeys, + setHotkeys, +} from "./hotkeys"; + +const formatKeypress = (kp: keypress): string => { + const parts: string[] = []; + if (kp.ctrlKey) parts.push("Ctrl"); + if (kp.altKey) parts.push("Alt"); + if (kp.shiftKey) parts.push("Shift"); + if (kp.metaKey) parts.push("Meta"); + parts.push(kp.key.toUpperCase()); + return parts.join(" + "); +}; + +const formatKeypressFromEvent = (e: KeyboardEvent): string => { + const parts: string[] = []; + if (e.ctrlKey) parts.push("Ctrl"); + if (e.altKey) parts.push("Alt"); + if (e.shiftKey) parts.push("Shift"); + if (e.metaKey) parts.push("Meta"); + parts.push(e.key.toUpperCase()); + return parts.join(" + "); +}; + +const formatActionName = (action: string): string => { + return action.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); +}; + +const CATEGORIES: Record = { + "Basic Actions": ["run", "eat", "lightFire"], + Equipment: ["equip1", "equip2", "equip3"], + Badges: ["badge1", "badge2", "badge3", "badge4"], + Teleports: [ + "teleport_everbrook", + "teleport_mysticvale", + "teleport_omboko", + "teleport_dock_haven", + "teleport_jafa_outpost", + "teleport_frostvale", + ], + "Worship Skills": [ + "remote_sell", + "dig", + "timers", + "auto_hell_burying", + "hunting_contact", + "mass_pickup", + "focus", + "clarity", + ], +}; + +const createModalStyles = (): HTMLStyleElement => { + const style = document.createElement("style"); + style.textContent = ` + #hotkeys-modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.7); + display: none; + justify-content: center; + z-index: 10000; + padding-top: 5vh; + } + + #hotkeys-modal-overlay.visible { + display: flex; + } + + #hotkeys-modal { + background: #1a1a2e; + border: 2px solid #4a4a6a; + border-radius: 8px; + max-width: 1000px; + max-height: 80vh; + width: 90%; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + height: fit-content; + padding-bottom: 20px; + } + + #hotkeys-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: #252540; + border-bottom: 1px solid #4a4a6a; + } + + #hotkeys-modal-header h2 { + margin: 0; + color: #e0e0e0; + font-size: 18px; + } + + #hotkeys-modal-close { + background: none; + border: none; + color: #888; + font-size: 24px; + cursor: pointer; + padding: 0; + line-height: 1; + } + + #hotkeys-modal-close:hover { + color: #fff; + } + + #hotkeys-modal-content { + overflow-y: auto; + max-height: calc(80vh - 60px); + padding: 16px; + } + + .hotkey-category { + font-size: 16px; + font-weight: 600; + color: #e0e0e0; + padding: 12px 0 8px 0; + border-bottom: 2px solid #4a4a6a; + margin-bottom: 8px; + } + + #hotkeys-grid { + display: flex; + flex-direction: column; + gap: 16px; + color: #e0e0e0; + } + + .category-section { + display: contents; + } + + .category-section.full-width { + display: flex; + flex-direction: column; + gap: 8px; + } + + .small-categories-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; + } + + .small-category-container { + display: flex; + flex-direction: column; + gap: 8px; + } + + .full-width .items-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 8px; + } + + .small-category-container .items-grid { + display: flex; + flex-direction: column; + gap: 8px; + } + + .hotkey-item { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 12px; + padding: 8px 12px; + border: 1px solid #333; + border-radius: 4px; + transition: background 0.15s ease; + } + + .hotkey-item:hover { + background: #252540; + } + + .hotkey-key { + display: inline-block; + background: #333; + border: 1px solid #555; + border-radius: 4px; + padding: 4px 12px; + font-family: monospace; + font-size: 14px; + cursor: pointer; + transition: all 0.15s ease; + } + + .hotkey-key:hover { + background: #444; + border-color: #777; + } + + .hotkey-key.recording { + background: #4a3a2e; + border-color: #f0a050; + } + + .hotkey-key.conflict { + background: #4a2a2a; + border-color: #c55; + } + `; + return style; +}; + +let currentlyRecording: HTMLSpanElement | null = null; +let recordingAction: keyof actions | null = null; + +const stopRecording = (restoreText = false) => { + if (currentlyRecording) { + if (restoreText && recordingAction) { + const currentHotkeys = mergeHotkeys(); + currentlyRecording.textContent = formatKeypress( + currentHotkeys[recordingAction], + ); + } + currentlyRecording.classList.remove("recording"); + currentlyRecording = null; + recordingAction = null; + } +}; + +// Track spans for conflict detection +const allSpans: HTMLSpanElement[] = []; + +// Mark conflicts +const updateConflicts = () => { + const currentHotkeys = mergeHotkeys(); + const hashCounts = Object.values(currentHotkeys) + .map(keypressToHashableString) + .reduce((acc: Record, hash) => { + acc[hash] = (acc[hash] || 0) + 1; + return acc; + }, {}); + + for (const span of allSpans) { + const action = span.dataset.action as keyof actions; + const kp = currentHotkeys[action]; + const hash = keypressToHashableString(kp); + const count = hashCounts[hash]; + if (count > 1) { + span.classList.add("conflict"); + } else { + span.classList.remove("conflict"); + } + } +}; + +const startRecording = (element: HTMLSpanElement, action: keyof actions) => { + // If clicking the same element, just stop + if (currentlyRecording === element) { + stopRecording(true); + return; + } + + // If recording a different one, stop it first + if (currentlyRecording) { + stopRecording(true); + } + + // Start recording on this element + currentlyRecording = element; + recordingAction = action; + element.classList.add("recording"); + element.textContent = "Press a key..."; +}; + +const getModifierText = (e: KeyboardEvent): string => { + const parts: string[] = []; + if (e.ctrlKey) parts.push("Ctrl"); + if (e.altKey) parts.push("Alt"); + if (e.shiftKey) parts.push("Shift"); + if (e.metaKey) parts.push("Meta"); + return parts.length > 0 ? `${parts.join(" + ")} + ...` : "Press a key..."; +}; + +const updateRecordingDisplay = (e: KeyboardEvent) => { + if (!currentlyRecording) return; + currentlyRecording.textContent = getModifierText(e); +}; + +const handleRecordingKeypress = (e: KeyboardEvent) => { + if (!currentlyRecording || !recordingAction) return; + + // Show modifiers while held + if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) { + updateRecordingDisplay(e); + return; + } + + e.preventDefault(); + e.stopPropagation(); + + const newKeypress: keypress = { + key: e.key, + altKey: e.altKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + shiftKey: e.shiftKey, + }; + + // Save immediately + setHotkeys({ [recordingAction]: newKeypress }); + + // Update the display + currentlyRecording.textContent = formatKeypressFromEvent(e); + currentlyRecording.classList.remove("recording"); + + // Update conflict highlighting + updateConflicts(); + + stopRecording(); +}; + +const handleRecordingKeyup = (e: KeyboardEvent) => { + if (!currentlyRecording) return; + + // Update display when modifiers are released + if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) { + updateRecordingDisplay(e); + } +}; + +const createModal = (): HTMLDivElement => { + const overlay = document.createElement("div"); + overlay.id = "hotkeys-modal-overlay"; + + const modal = document.createElement("div"); + modal.id = "hotkeys-modal"; + + const header = document.createElement("div"); + header.id = "hotkeys-modal-header"; + + const title = document.createElement("h2"); + title.textContent = "Hotkey Bindings"; + + const closeBtn = document.createElement("button"); + closeBtn.id = "hotkeys-modal-close"; + closeBtn.textContent = "×"; + closeBtn.addEventListener("click", () => hideModal()); + + header.appendChild(title); + header.appendChild(closeBtn); + + const content = document.createElement("div"); + content.id = "hotkeys-modal-content"; + + const grid = document.createElement("div"); + grid.id = "hotkeys-grid"; + + const hotkeys = mergeHotkeys(); + + // Group categories by size + const smallCategories: [string, (keyof actions)[]][] = []; + const largeCategories: [string, (keyof actions)[]][] = []; + + for (const [categoryName, categoryActions] of Object.entries(CATEGORIES)) { + if (categoryActions.length <= 4) { + smallCategories.push([categoryName, categoryActions]); + } else { + largeCategories.push([categoryName, categoryActions]); + } + } + + // Helper function to create category items + const createCategoryItems = ( + categoryActions: (keyof actions)[], + ): HTMLElement[] => { + const items: HTMLElement[] = []; + for (const action of categoryActions) { + const kp = hotkeys[action]; + const actionInfo = ACTIONS[action]; + if (!actionInfo || !kp) continue; + + const item = document.createElement("div"); + item.className = "hotkey-item"; + + const actionName = document.createElement("span"); + actionName.className = "hotkey-action"; + actionName.textContent = formatActionName(action); + + const hotkeySpan = document.createElement("span"); + hotkeySpan.className = "hotkey-key"; + hotkeySpan.textContent = formatKeypress(kp); + hotkeySpan.dataset.action = action; + + allSpans.push(hotkeySpan); + + hotkeySpan.addEventListener("click", () => { + startRecording(hotkeySpan, action); + }); + + item.appendChild(actionName); + item.appendChild(hotkeySpan); + items.push(item); + } + return items; + }; + + // Add small categories in a row + if (smallCategories.length > 0) { + const smallCategoriesRow = document.createElement("div"); + smallCategoriesRow.className = "small-categories-row"; + + for (const [categoryName, categoryActions] of smallCategories) { + const container = document.createElement("div"); + container.className = "small-category-container"; + + const categoryHeader = document.createElement("div"); + categoryHeader.className = "hotkey-category"; + categoryHeader.textContent = categoryName; + container.appendChild(categoryHeader); + + const itemsGrid = document.createElement("div"); + itemsGrid.className = "items-grid"; + const items = createCategoryItems(categoryActions); + items.forEach((item) => itemsGrid.appendChild(item)); + container.appendChild(itemsGrid); + + smallCategoriesRow.appendChild(container); + } + + grid.appendChild(smallCategoriesRow); + } + + // Add large categories + for (const [categoryName, categoryActions] of largeCategories) { + const section = document.createElement("div"); + section.className = "category-section full-width"; + + const categoryHeader = document.createElement("div"); + categoryHeader.className = "hotkey-category"; + categoryHeader.textContent = categoryName; + section.appendChild(categoryHeader); + + const itemsGrid = document.createElement("div"); + itemsGrid.className = "items-grid"; + const items = createCategoryItems(categoryActions); + items.forEach((item) => itemsGrid.appendChild(item)); + section.appendChild(itemsGrid); + + grid.appendChild(section); + } + + updateConflicts(); + + content.appendChild(grid); + + modal.appendChild(header); + modal.appendChild(content); + overlay.appendChild(modal); + + overlay.addEventListener("click", (e) => { + if (e.target === overlay) { + hideModal(); + } + }); + + // Stop recording when clicking elsewhere in the modal + modal.addEventListener("click", (e) => { + const target = e.target as HTMLElement; + if (!target.classList.contains("hotkey-key") && currentlyRecording) { + stopRecording(true); + } + }); + + return overlay; +}; + +let modalElement: HTMLDivElement | null = null; + +const initModal = () => { + if (modalElement) return; + document.head.appendChild(createModalStyles()); + modalElement = createModal(); + document.body.appendChild(modalElement); +}; + +export const showModal = () => { + initModal(); + modalElement?.classList.add("visible"); +}; + +export const hideModal = () => { + modalElement?.classList.remove("visible"); +}; + +export const toggleModal = () => { + initModal(); + if (modalElement?.classList.contains("visible")) { + hideModal(); + } else { + showModal(); + } +}; + +// Listen for keypresses when modal is open +document.addEventListener("keydown", (e) => { + if (!modalElement?.classList.contains("visible")) return; + + // If recording, handle the keypress + if (currentlyRecording) { + // Escape cancels recording + if (e.key === "Escape") { + stopRecording(true); + e.preventDefault(); + return; + } + + handleRecordingKeypress(e); + return; + } + + // Escape closes modal when not recording + if (e.key === "Escape") { + hideModal(); + e.preventDefault(); + } +}); + +// Listen for key releases to update modifier display +document.addEventListener("keyup", (e) => { + if (!modalElement?.classList.contains("visible")) return; + handleRecordingKeyup(e); +}); diff --git a/table-links.user.js b/table-links.user.js deleted file mode 100644 index 4e38cd6..0000000 --- a/table-links.user.js +++ /dev/null @@ -1,60 +0,0 @@ -// ==UserScript== -// @name FlatMMO Wiki Table Links -// @namespace Violentmonkey Scripts -// @match https://flatmmo.wiki/index.php/* -// @grant none -// @version 1.0 -// @author - -// @description Adds anchor links to wikitable rows based on first column text -// ==/UserScript== - -(function() { - 'use strict'; - - // Find all wikitable tables - const tables = document.querySelectorAll('table.wikitable'); - - tables.forEach(table => { - const rows = table.querySelectorAll('tbody tr'); - - rows.forEach(row => { - // Skip header rows (rows with th elements) - if (row.querySelector('th')) return; - - // Get first column - const firstCell = row.querySelector('td'); - if (!firstCell) return; - - // Get text content and create ID - const text = firstCell.textContent.trim(); - if (!text) return; - - // Create a URL-safe ID from the text - const id = text.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, ''); - - // Set ID on the row - row.id = id; - - // Create anchor link - const link = document.createElement('a'); - link.href = `#${id}`; - link.textContent = '#'; - link.style.marginLeft = '5px'; - link.style.textDecoration = 'none'; - link.style.color = '#999'; - link.style.fontSize = '0.9em'; - link.title = `Link to ${text}`; - - // Add hover effect - link.addEventListener('mouseenter', () => { - link.style.color = '#0645ad'; - }); - link.addEventListener('mouseleave', () => { - link.style.color = '#999'; - }); - - // Add link to first cell - firstCell.appendChild(link); - }); - }); -})(); \ No newline at end of file diff --git a/test/UserscriptMetadata.test.ts b/test/UserscriptMetadata.test.ts new file mode 100644 index 0000000..cc1ddee --- /dev/null +++ b/test/UserscriptMetadata.test.ts @@ -0,0 +1,326 @@ +import { describe, expect, test } from "bun:test"; +import { + generateMetadataString, + type ViolentmonkeyMetadata, +} from "../packages/build/UserscriptMetadata"; + +describe("generateMetadataString", () => { + test("generates basic metadata with required fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// ==UserScript=="); + expect(result).toContain("// ==/UserScript=="); + expect(result).toContain("// @name Test Script"); + }); + + test("handles optional string fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + namespace: "https://example.com", + version: "1.0.0", + description: "A test script", + icon: "https://example.com/icon.png", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @name Test Script"); + expect(result).toContain("// @namespace https://example.com"); + expect(result).toContain("// @version 1.0.0"); + expect(result).toContain("// @description A test script"); + expect(result).toContain("// @icon https://example.com/icon.png"); + }); + + test("handles list fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + match: ["https://example.com/*", "https://test.com/*"], + grant: ["GM.getValue", "GM.setValue"], + require: ["https://cdn.example.com/lib.js"], + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @match https://example.com/*"); + expect(result).toContain("// @match https://test.com/*"); + expect(result).toContain("// @grant GM.getValue"); + expect(result).toContain("// @grant GM.setValue"); + expect(result).toContain("// @require https://cdn.example.com/lib.js"); + }); + + test("handles boolean fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + noframes: true, + unwrap: true, + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @noframes"); + expect(result).toContain("// @unwrap"); + }); + + test("handles locale fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + "name:zh-CN": "测试脚本", + "name:ja": "テストスクリプト", + description: "A test script", + "description:zh-CN": "测试脚本描述", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @name Test Script"); + expect(result).toContain("// @name:zh-CN 测试脚本"); + expect(result).toContain("// @name:ja テストスクリプト"); + expect(result).toContain("// @description A test script"); + expect(result).toContain("// @description:zh-CN 测试脚本描述"); + }); + + test("handles resource fields with special format", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + resource: [ + { name: "logo", url: "https://example.com/logo.png" }, + { name: "config", url: "https://example.com/config.json" }, + ], + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain( + "// @resource logo https://example.com/logo.png", + ); + expect(result).toContain( + "// @resource config https://example.com/config.json", + ); + }); + + test("handles run-at field", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + "run-at": "document-idle", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @run-at document-idle"); + }); + + test("handles inject-into field", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + "inject-into": "page", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @inject-into page"); + }); + + test("handles top-level-await field", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + "top-level-await": true, + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// @top-level-await"); + }); + + test("handles URL fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + downloadURL: "https://example.com/script.user.js", + supportURL: "https://example.com/support", + homepageURL: "https://example.com", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain( + "// @downloadURL https://example.com/script.user.js", + ); + expect(result).toContain("// @supportURL https://example.com/support"); + expect(result).toContain("// @homepageURL https://example.com"); + }); + + test("omits undefined optional fields", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + + expect(result).not.toContain("// @namespace"); + expect(result).not.toContain("// @version"); + expect(result).not.toContain("// @description"); + expect(result).not.toContain("// @match"); + expect(result).not.toContain("// @grant"); + }); + + test("generates complete real-world example", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Custom Action UI", + "name:zh-CN": "自定义动作界面", + namespace: "Joshu FlatMMO Scripts", + version: "1.0.0", + description: + "ui tweaks to make it easier to tell at what action your character is doing", + "description:zh-CN": "UI调整,更容易看出角色正在执行什么动作", + match: ["https://flatmmo.com/play.php*"], + grant: ["GM.getValue", "GM.setValue"], + "inject-into": "page", + "run-at": "document-idle", + }; + + const result = generateMetadataString(metadata); + + expect(result).toContain("// ==UserScript=="); + expect(result).toContain("// @name Custom Action UI"); + expect(result).toContain("// @name:zh-CN 自定义动作界面"); + expect(result).toContain("// @namespace Joshu FlatMMO Scripts"); + expect(result).toContain("// @version 1.0.0"); + expect(result).toContain( + "// @description ui tweaks to make it easier to tell at what action your character is doing", + ); + expect(result).toContain( + "// @description:zh-CN UI调整,更容易看出角色正在执行什么动作", + ); + expect(result).toContain("// @match https://flatmmo.com/play.php*"); + expect(result).toContain("// @grant GM.getValue"); + expect(result).toContain("// @grant GM.setValue"); + expect(result).toContain("// @inject-into page"); + expect(result).toContain("// @run-at document-idle"); + expect(result).toContain("// ==/UserScript=="); + }); + + test("handles empty arrays", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + match: [], + grant: [], + }; + + const result = generateMetadataString(metadata); + + // Empty arrays should not generate any lines + expect(result).not.toContain("// @match"); + expect(result).not.toContain("// @grant"); + }); + + describe("strict formatting requirements", () => { + test("first line must be exactly // ==UserScript==", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + const lines = result.split("\n").filter((line) => line.trim()); + + expect(lines[0]).toBe("// ==UserScript=="); + }); + + test("last line must be exactly // ==/UserScript==", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + const lines = result.split("\n").filter((line) => line.trim()); + + expect(lines[lines.length - 1]).toBe("// ==/UserScript=="); + }); + + test("every line must start with //", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + namespace: "https://example.com", + version: "1.0.0", + match: ["https://example.com/*"], + grant: ["GM.getValue"], + }; + + const result = generateMetadataString(metadata); + const lines = result.split("\n").filter((line) => line.trim()); + + lines.forEach((line) => { + expect(line.startsWith("//")).toBe(true); + }); + }); + + test("every line must have exactly one space after //", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + namespace: "https://example.com", + version: "1.0.0", + description: "A test", + match: ["https://example.com/*"], + grant: ["GM.getValue"], + noframes: true, + }; + + const result = generateMetadataString(metadata); + const lines = result.split("\n").filter((line) => line.trim()); + + lines.forEach((line) => { + // After //, there should be exactly one space + expect(line.match(/^\/\/ /)).toBeTruthy(); + // Should not have multiple spaces after // + expect(line.match(/^\/\/ {2}/)).toBeFalsy(); + }); + }); + + test("no extraneous text at beginning of metadata block", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + + // Should not start with whitespace or other characters before // + expect(result.trimStart()).toBe(result); + expect(result.startsWith("//")).toBe(true); + }); + + test("no extraneous text at end of metadata block", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + }; + + const result = generateMetadataString(metadata); + + // Should end with ==/UserScript== and newline, no extra whitespace + expect(result.trimEnd() + "\n").toBe(result); + expect(result.trim().endsWith("// ==/UserScript==")).toBe(true); + }); + + test("no blank lines within metadata block", () => { + const metadata: ViolentmonkeyMetadata = { + name: "Test Script", + namespace: "https://example.com", + version: "1.0.0", + match: ["https://example.com/*"], + }; + + const result = generateMetadataString(metadata); + const lines = result.split("\n"); + + // Find the metadata block (between ==UserScript== markers) + const startIdx = lines.findIndex((l) => l.includes("==UserScript==")); + const endIdx = lines.findIndex((l) => l.includes("==/UserScript==")); + + // Check there are no empty lines within the block + for (let i = startIdx + 1; i < endIdx; i++) { + expect(lines[i].trim()).not.toBe(""); + } + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1e2f078 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "types": ["@violentmonkey/types", "bun", "flatmmo-types"], + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..4a19bb2 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,6 @@ +# Generated by Wrangler on Sun Dec 28 2025 19:40:36 GMT-0500 (Eastern Standard Time) +name = "flatmmo-userscripts" +compatibility_date = "2025-12-17" +pages_build_output_dir = "result/static" + +[env.production]